Commit 71f98397 by Ooh-Ao

widgets

parent ddbcfa1f
......@@ -75,6 +75,12 @@
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Photo URL Field (Optional)</mat-label>
<mat-select [(ngModel)]="currentConfig.photoField" name="photoField">
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="widgetType === 'HeadcountWidgetComponent'">
......@@ -88,6 +94,13 @@
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Chart Type</mat-label>
<mat-select [(ngModel)]="currentConfig.chartType" name="chartType">
<mat-option value="bar">Bar Chart</mat-option>
<mat-option value="doughnut">Doughnut Chart</mat-option>
</mat-select>
</mat-form-field>
</div>
<div *ngIf="widgetType === 'PayrollSummaryWidgetComponent'">
......@@ -135,11 +148,25 @@
</div>
<div *ngIf="widgetType === 'WelcomeWidgetComponent'">
<mat-form-field appearance="fill">
<mat-form-field appearance="fill" class="w-full">
<mat-label>Title</mat-label>
<input matInput [(ngModel)]="currentConfig.title" name="title">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-form-field appearance="fill" class="w-full">
<mat-label>Message Source</mat-label>
<mat-select [(ngModel)]="currentConfig.messageType" name="messageType">
<mat-option value="static">Static Text</mat-option>
<mat-option value="dynamic">From Data Field</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="currentConfig.messageType === 'static'" appearance="fill" class="w-full">
<mat-label>Static Message</mat-label>
<input matInput [(ngModel)]="currentConfig.staticMessage" name="staticMessage">
</mat-form-field>
<mat-form-field *ngIf="currentConfig.messageType === 'dynamic'" appearance="fill" class="w-full">
<mat-label>Message Field</mat-label>
<mat-select [(ngModel)]="currentConfig.messageField" name="messageField">
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
......@@ -196,6 +223,12 @@
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Icon Field (Optional)</mat-label>
<mat-select [(ngModel)]="currentConfig.iconField" name="iconField">
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
</div>
<!-- Unified Column Editor for Grid-like Widgets -->
......
......@@ -61,6 +61,34 @@ export class WidgetConfigGeneratorService {
newConfig.title = widget.thName;
newConfig.optionsField = col1;
break;
case 'WelcomeWidgetComponent':
newConfig.title = widget.thName;
newConfig.messageType = 'static';
newConfig.staticMessage = 'Welcome!';
newConfig.messageField = col1;
break;
case 'QuickLinksWidgetComponent':
newConfig.title = widget.thName;
newConfig.nameField = col1;
newConfig.urlField = col2;
newConfig.iconField = 'icon'; // Assuming a column named 'icon' might exist
break;
case 'HeadcountWidgetComponent':
newConfig.title = widget.thName;
newConfig.categoryField = col1;
newConfig.chartType = 'bar';
break;
case 'EmployeeDirectoryWidgetComponent':
newConfig.title = widget.thName;
newConfig.nameField = col1;
newConfig.positionField = col2;
newConfig.departmentField = columns.length > 2 ? columns[2] : col1;
newConfig.photoField = 'photoUrl';
break;
default:
newConfig.title = widget.thName;
......
<div class="bg-white rounded-xl shadow-lg flex flex-col h-full">
<!-- Header -->
<div class="flex-shrink-0 p-4 sm:p-6 border-b border-gray-200">
<h4 class="text-md sm:text-lg font-semibold text-gray-600 truncate">{{ title }}</h4>
</div>
<!-- Body -->
<div class="flex-grow p-4 sm:p-6 overflow-auto">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-circle-fill text-4xl"></i>
<p class="mt-2 font-semibold">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError" class="grid grid-cols-1 sm:grid-cols-3 gap-4 h-full">
<!-- Present -->
<div class="flex flex-col items-center justify-center p-4 bg-green-50 rounded-lg">
<i class="bi bi-person-check-fill text-3xl text-green-500"></i>
<p class="mt-2 text-3xl font-bold text-green-800">{{ present }}</p>
<p class="text-sm font-medium text-green-600">Present</p>
</div>
<!-- On Leave -->
<div class="flex flex-col items-center justify-center p-4 bg-yellow-50 rounded-lg">
<i class="bi bi-person-dash-fill text-3xl text-yellow-500"></i>
<p class="mt-2 text-3xl font-bold text-yellow-800">{{ onLeave }}</p>
<p class="text-sm font-medium text-yellow-600">On Leave</p>
</div>
<!-- Absent -->
<div class="flex flex-col items-center justify-center p-4 bg-red-50 rounded-lg">
<i class="bi bi-person-x-fill text-3xl text-red-500"></i>
<p class="mt-2 text-3xl font-bold text-red-800">{{ absent }}</p>
<p class="text-sm font-medium text-red-600">Absent</p>
</div>
</div>
</div>
</div>
......@@ -8,30 +8,7 @@ import { BaseWidgetComponent } from './base-widget.component';
selector: 'app-attendance-overview-widget',
standalone: true,
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-yellow-50">
<h3 class="font-bold text-yellow-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-yellow-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-yellow-600">Present: {{ present }}</p>
<p class="text-sm text-yellow-600">On Leave: {{ onLeave }}</p>
<p class="text-sm text-yellow-600">Absent: {{ absent }}</p>
</div>
</div>
`
templateUrl: './attendance-overview-widget.component.html'
})
export class AttendanceOverviewWidgetComponent extends BaseWidgetComponent {
public present: number = 0;
......
<div class="bg-white rounded-xl shadow-lg flex flex-col h-full">
<!-- Header -->
<div class="flex-shrink-0 p-4 sm:p-6 border-b border-gray-200">
<h4 class="text-md sm:text-lg font-semibold text-gray-600 truncate">{{ title }}</h4>
</div>
<!-- Body -->
<div class="flex-grow p-4 sm:p-6 overflow-auto">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-circle-fill text-4xl"></i>
<p class="mt-2 font-semibold">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError" class="space-y-4">
<div *ngFor="let employee of employees" class="flex items-center p-3 -m-3 rounded-lg hover:bg-gray-50 transition ease-in-out duration-150">
<div class="flex-shrink-0">
<img *ngIf="employee.photoUrl" [src]="employee.photoUrl" alt="" class="h-12 w-12 rounded-full object-cover">
<div *ngIf="!employee.photoUrl" class="h-12 w-12 rounded-full bg-gray-200 flex items-center justify-center">
<i class="bi bi-person-fill text-2xl text-gray-500"></i>
</div>
</div>
<div class="ml-4">
<p class="text-sm font-medium text-gray-900">{{ employee.name }}</p>
<p class="text-sm text-gray-500">{{ employee.position }}</p>
<p class="text-xs text-gray-400">{{ employee.department }}</p>
</div>
</div>
<div *ngIf="employees.length === 0" class="text-center text-gray-400 py-8">
<i class="bi bi-people text-4xl"></i>
<p class="mt-2">No employees to display.</p>
</div>
</div>
</div>
</div>
......@@ -4,40 +4,21 @@ import { CommonModule } from '@angular/common';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
export interface Employee {
name: string;
position: string;
department: string;
photoUrl?: string;
}
@Component({
selector: 'app-employee-directory-widget',
standalone: true,
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-indigo-50">
<h3 class="font-bold text-indigo-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-indigo-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<div *ngIf="employees.length > 0">
<div *ngFor="let employee of employees" class="mb-2">
<p class="text-sm text-indigo-600">{{ employee.name }} - {{ employee.position }} ({{ employee.department }})</p>
</div>
</div>
<p *ngIf="employees.length === 0" class="text-sm text-indigo-600">No employees found.</p>
</div>
</div>
`
templateUrl: './employee-directory-widget.component.html'
})
export class EmployeeDirectoryWidgetComponent extends BaseWidgetComponent {
public employees: any[] = [];
public employees: Employee[] = [];
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
......@@ -53,14 +34,16 @@ export class EmployeeDirectoryWidgetComponent extends BaseWidgetComponent {
name: item[this.config.nameField] || '',
position: item[this.config.positionField] || '',
department: item[this.config.departmentField] || '',
photoUrl: this.config.photoField ? item[this.config.photoField] : undefined
}));
}
onReset(): void {
this.title = 'Employee Directory (Default)';
this.employees = [
{ name: 'John Doe', position: 'Software Engineer', department: 'IT' },
{ name: 'Jane Smith', position: 'Project Manager', department: 'Operations' },
{ name: 'John Doe', position: 'Software Engineer', department: 'IT', photoUrl: 'https://randomuser.me/api/portraits/men/1.jpg' },
{ name: 'Jane Smith', position: 'Project Manager', department: 'Operations', photoUrl: 'https://randomuser.me/api/portraits/women/2.jpg' },
{ name: 'Peter Jones', position: 'UX Designer', department: 'Design', photoUrl: 'https://randomuser.me/api/portraits/men/3.jpg' },
];
}
}
<div class="bg-white rounded-xl shadow-lg flex flex-col h-full">
<!-- Header -->
<div class="flex-shrink-0 p-4 sm:p-6 border-b border-gray-200">
<h4 class="text-md sm:text-lg font-semibold text-gray-600 truncate">{{ title }}</h4>
<p class="text-sm text-gray-500">Total: {{ totalHeadcount }}</p>
</div>
<!-- Body -->
<div class="flex-grow p-2 sm:p-4 overflow-auto">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-circle-fill text-4xl"></i>
<p class="mt-2 font-semibold">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError" class="h-full">
<!-- Bar Chart -->
<ejs-chart *ngIf="config.chartType === 'bar'" [primaryXAxis]="primaryXAxis" [primaryYAxis]="primaryYAxis" [tooltip]="tooltip" height="100%">
<e-series-collection>
<e-series [dataSource]="breakdown" type="Bar" xName="category" yName="count" name="Headcount"></e-series>
</e-series-collection>
</ejs-chart>
<!-- Doughnut Chart -->
<ejs-accumulationchart *ngIf="config.chartType === 'doughnut'" [legendSettings]="legendSettings" [tooltip]="tooltip" height="100%">
<e-accumulation-series-collection>
<e-accumulation-series [dataSource]="breakdown" xName="category" yName="count" innerRadius="40%" [dataLabel]="dataLabel"></e-accumulation-series>
</e-accumulation-series-collection>
</ejs-accumulationchart>
</div>
</div>
</div>
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ChartAllModule, AccumulationChartAllModule } from '@syncfusion/ej2-angular-charts';
import { DashboardStateService } from '../services/dashboard-state.service';
import { BaseWidgetComponent } from './base-widget.component';
@Component({
selector: 'app-headcount-widget',
standalone: true,
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-green-50">
<h3 class="font-bold text-green-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-green-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-green-600">Total Headcount: {{ totalHeadcount }}</p>
<div *ngIf="breakdown.length > 0">
<p class="text-sm text-green-600">By {{ config?.categoryField || 'Category' }}:</p>
<ul>
<li *ngFor="let item of breakdown" class="text-xs text-green-600">
{{ item.category }}: {{ item.count }}
</li>
</ul>
</div>
</div>
</div>
`
imports: [CommonModule, ChartAllModule, AccumulationChartAllModule],
templateUrl: './headcount-widget.component.html'
})
export class HeadcountWidgetComponent extends BaseWidgetComponent {
public totalHeadcount: number = 0;
public breakdown: { category: string, count: number }[] = [];
// Chart properties
public primaryXAxis: Object;
public primaryYAxis: Object;
public legendSettings: Object;
public dataLabel: Object;
public tooltip: Object;
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
}
......@@ -51,6 +30,23 @@ export class HeadcountWidgetComponent extends BaseWidgetComponent {
this.title = this.config.title || 'Headcount';
this.totalHeadcount = 0;
this.breakdown = [];
// Initialize chart settings
this.primaryXAxis = {
valueType: 'Category',
majorGridLines: { width: 0 }
};
this.primaryYAxis = {
majorTickLines: { width: 0 },
lineStyle: { width: 0 },
labelStyle: { color: 'transparent' }
};
this.legendSettings = {
visible: true,
position: 'Bottom'
};
this.dataLabel = { visible: true, name: 'count', position: 'Inside' };
this.tooltip = { enable: true };
}
onDataUpdate(data: any[]): void {
......
<div class="bg-white rounded-xl shadow-lg flex flex-col h-full">
<!-- Header -->
<div class="flex-shrink-0 p-4 sm:p-6 border-b border-gray-200">
<h4 class="text-md sm:text-lg font-semibold text-gray-600 truncate">{{ title }}</h4>
</div>
<!-- Body -->
<div class="flex-grow p-4 sm:p-6 overflow-auto">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-circle-fill text-4xl"></i>
<p class="mt-2 font-semibold">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError" class="grid grid-cols-1 sm:grid-cols-2 gap-4 h-full">
<!-- Total Payroll -->
<div class="flex flex-col items-center justify-center p-4 bg-blue-50 rounded-lg">
<i class="bi bi-cash-stack text-3xl text-blue-500"></i>
<p class="mt-2 text-3xl font-bold text-blue-800">{{ totalPayroll | currency:'USD':'symbol':'1.0-0' }}</p>
<p class="text-sm font-medium text-blue-600">Total Payroll</p>
</div>
<!-- Employees Paid -->
<div class="flex flex-col items-center justify-center p-4 bg-purple-50 rounded-lg">
<i class="bi bi-people-fill text-3xl text-purple-500"></i>
<p class="mt-2 text-3xl font-bold text-purple-800">{{ employeesPaid }}</p>
<p class="text-sm font-medium text-purple-600">Employees Paid</p>
</div>
</div>
</div>
</div>
......@@ -8,29 +8,7 @@ import { BaseWidgetComponent } from './base-widget.component';
selector: 'app-payroll-summary-widget',
standalone: true,
imports: [CommonModule],
template: `
<div class="p-4 h-full bg-red-50">
<h3 class="font-bold text-red-800">{{ title }}</h3>
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-red-500"></div>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-2xl"></i>
<p class="mt-1 text-sm">{{ errorMessage }}</p>
</div>
<!-- Content -->
<div *ngIf="!isLoading && !hasError">
<p class="text-sm text-red-600">Total Payroll: {{ totalPayroll | currency }}</p>
<p class="text-sm text-red-600">Employees Paid: {{ employeesPaid }}</p>
</div>
</div>
`
templateUrl: './payroll-summary-widget.component.html'
})
export class PayrollSummaryWidgetComponent extends BaseWidgetComponent {
public totalPayroll: number = 0;
......
......@@ -23,7 +23,7 @@
<ul *ngIf="!isLoading && !hasError" class="space-y-3">
<li *ngFor="let link of quickLinks">
<a [href]="link.url" target="_blank" class="flex items-center p-3 -m-3 rounded-lg hover:bg-gray-50 transition ease-in-out duration-150">
<i class="bi bi-link-45deg text-blue-500 text-xl"></i>
<i [class]="'bi bi-' + (link.icon || 'link-45deg') + ' text-blue-500 text-xl'"></i>
<span class="ml-3 font-medium text-gray-700 hover:text-blue-600">{{ link.name }}</span>
</a>
</li>
......
......@@ -10,7 +10,7 @@ import { BaseWidgetComponent } from '../base-widget.component';
templateUrl: './quick-links-widget.component.html',
})
export class QuickLinksWidgetComponent extends BaseWidgetComponent {
public quickLinks: { name: string, url: string }[] = [];
public quickLinks: { name: string, url: string, icon?: string }[] = [];
constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService);
......@@ -25,7 +25,8 @@ export class QuickLinksWidgetComponent extends BaseWidgetComponent {
if (this.config.nameField && this.config.urlField) {
this.quickLinks = data.map(item => ({
name: item[this.config.nameField],
url: item[this.config.urlField]
url: item[this.config.urlField],
icon: this.config.iconField ? item[this.config.iconField] : 'link-45deg'
}));
}
}
......@@ -33,9 +34,9 @@ export class QuickLinksWidgetComponent extends BaseWidgetComponent {
onReset(): void {
this.title = 'Quick Links (Default)';
this.quickLinks = [
{ name: 'Google', url: 'https://www.google.com' },
{ name: 'Angular', url: 'https://angular.io' },
{ name: 'Syncfusion', url: 'https://www.syncfusion.com' },
{ name: 'Google', url: 'https://www.google.com', icon: 'google' },
{ name: 'Angular', url: 'https://angular.io', icon: 'gem' },
{ name: 'Syncfusion', url: 'https://www.syncfusion.com', icon: 'code-square' },
];
}
}
<div class="card h-100">
<div class="card-body flex flex-col justify-center items-center">
<div class="bg-white rounded-xl shadow-lg p-4 sm:p-6 flex flex-col h-full">
<!-- Header -->
<div class="flex-shrink-0 border-b border-gray-200 pb-4 mb-4" *ngIf="title">
<h4 class="text-md sm:text-lg font-semibold text-gray-600 truncate">{{ title }}</h4>
</div>
<!-- Body -->
<div class="flex-grow flex justify-center items-center">
<!-- Loading State -->
<div *ngIf="isLoading" class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500"></div>
<div *ngIf="isLoading" class="text-center">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500 mx-auto"></div>
<p class="text-gray-400 mt-2">Loading...</p>
</div>
<!-- Error State -->
<div *ngIf="hasError" class="flex flex-col justify-center items-center h-full text-red-500">
<i class="bi bi-exclamation-triangle-fill text-4xl"></i>
<p class="mt-2">{{ errorMessage }}</p>
<div *ngIf="hasError" class="text-center text-red-500">
<i class="bi bi-exclamation-circle-fill text-4xl"></i>
<p class="mt-2 font-semibold">Error</p>
<p class="text-sm text-red-400">{{ errorMessage }}</p>
</div>
<!-- Content -->
<h3 *ngIf="!isLoading && !hasError" class="text-2xl font-bold text-gray-800">{{ welcomeMessage }}</h3>
<div *ngIf="!isLoading && !hasError" class="text-center">
<h3 class="text-2xl font-bold text-gray-800">{{ welcomeMessage }}</h3>
</div>
</div>
</div>
......@@ -17,13 +17,24 @@ export class WelcomeWidgetComponent extends BaseWidgetComponent {
}
applyInitialConfig(): void {
this.title = this.config.title || '';
this.welcomeMessage = this.config.message || '...';
this.title = this.config.title || 'Welcome';
if (this.config.messageType === 'static') {
this.welcomeMessage = this.config.staticMessage || 'Welcome!';
} else {
this.welcomeMessage = '...'; // Placeholder while waiting for data
}
}
onDataUpdate(data: any[]): void {
if (data.length > 0 && this.config.messageField) {
this.welcomeMessage = data[0][this.config.messageField];
if (this.config.messageType === 'dynamic') {
if (data.length > 0 && this.config.messageField) {
this.welcomeMessage = data[0][this.config.messageField];
} else {
this.welcomeMessage = 'No message data';
}
} else {
// For static messages, the message is already set in applyInitialConfig
this.welcomeMessage = this.config.staticMessage || 'Welcome!';
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment