Commit e1eb60f1 by Ooh-Ao

manage widget

parent b2633955
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
"@syncfusion/ej2-angular-inputs": "^29.2.4", "@syncfusion/ej2-angular-inputs": "^29.2.4",
"@syncfusion/ej2-angular-layouts": "^29.2.4", "@syncfusion/ej2-angular-layouts": "^29.2.4",
"@syncfusion/ej2-angular-maps": "^29.1.33", "@syncfusion/ej2-angular-maps": "^29.1.33",
"@syncfusion/ej2-angular-navigations": "^29.2.4",
"@syncfusion/ej2-angular-pivotview": "^29.2.4", "@syncfusion/ej2-angular-pivotview": "^29.2.4",
"@syncfusion/ej2-angular-popups": "^29.2.8", "@syncfusion/ej2-angular-popups": "^29.2.8",
"@syncfusion/ej2-angular-treemap": "^29.2.4", "@syncfusion/ej2-angular-treemap": "^29.2.4",
...@@ -6316,6 +6317,31 @@ ...@@ -6316,6 +6317,31 @@
"@syncfusion/ej2-maps": "29.2.11" "@syncfusion/ej2-maps": "29.2.11"
} }
}, },
"node_modules/@syncfusion/ej2-angular-navigations": {
"version": "29.2.4",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-navigations/-/ej2-angular-navigations-29.2.4.tgz",
"integrity": "sha512-+tT4w4NigZGK7/+m9zpku2lgcmiZIb6DORWwxyQ19pWFs/tqST5cryeJojWxp8UUTSaWu/OEpTbfH8jhJ+VUnw==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-angular-base": "~29.2.4",
"@syncfusion/ej2-base": "~29.2.4",
"@syncfusion/ej2-navigations": "29.2.4"
}
},
"node_modules/@syncfusion/ej2-angular-navigations/node_modules/@syncfusion/ej2-navigations": {
"version": "29.2.4",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-navigations/-/ej2-navigations-29.2.4.tgz",
"integrity": "sha512-7uP3giD2VyegHH8h2mfo8QGMxC903JLZg/3EDY5PUOqG1IqlnVjBRoQrj5nclxANGyq/R8v3cYpn42+x+ZzcAQ==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~29.2.4",
"@syncfusion/ej2-buttons": "~29.2.4",
"@syncfusion/ej2-data": "~29.2.4",
"@syncfusion/ej2-inputs": "~29.2.4",
"@syncfusion/ej2-lists": "~29.2.4",
"@syncfusion/ej2-popups": "~29.2.4"
}
},
"node_modules/@syncfusion/ej2-angular-pivotview": { "node_modules/@syncfusion/ej2-angular-pivotview": {
"version": "29.2.10", "version": "29.2.10",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-pivotview/-/ej2-angular-pivotview-29.2.10.tgz", "resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-pivotview/-/ej2-angular-pivotview-29.2.10.tgz",
......
...@@ -27,7 +27,7 @@ export class WidgetService { ...@@ -27,7 +27,7 @@ export class WidgetService {
downloadFile(logId: string): Observable<any> { downloadFile(logId: string): Observable<any> {
return this.http.get(this.url + "widget-registry/files/download/" + logId, { responseType: 'blob' }) return this.http.get(this.url + "widget-registry/files/download/" + logId, { responseType: 'blob' })
} }
createWidget(model: WidgetModel): Observable<any> { saveWidget(model: WidgetModel): Observable<any> {
let body: any = model let body: any = model
return this.http.post(this.url + 'widget-registry', body) return this.http.post(this.url + 'widget-registry', body)
} }
......
<div class="container mx-auto p-6 bg-gray-50"> <div class="container mx-auto p-6 bg-gray-50">
<h1 class="text-3xl font-extrabold mb-6 text-gray-800">{{ isNew ? 'Add New Widget' : 'Edit Widget' }}</h1> <h1 class="text-3xl font-extrabold mb-6 text-gray-800">
{{ isNew ? 'Add New Widget' : 'Edit Widget' }}
<form [formGroup]="widgetForm" (ngSubmit)="onSubmit()" class="bg-white shadow-xl rounded-xl px-8 pt-8 pb-8 mb-4"> </h1>
<form
[formGroup]="widgetForm"
(ngSubmit)="onSubmit()"
class="bg-white shadow-xl rounded-xl px-8 pt-8 pb-8 mb-4"
>
<!-- Widget Name (Thai) --> <!-- Widget Name (Thai) -->
<div class="mb-6"> <div class="mb-6">
<label class="block text-gray-700 text-sm font-bold mb-2" for="thName"> <label class="block text-gray-700 text-sm font-bold mb-2" for="thName">
Widget Name (Thai) Widget Name (Thai)
</label> </label>
<input formControlName="thName" id="thName" type="text" placeholder="เช่น ข่าวสารบริษัท" <ejs-textbox
class="shadow-sm border border-gray-200 rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> formControlName="thName"
id="thName"
placeholder="เช่น ข่าวสารบริษัท"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-textbox>
</div> </div>
<!-- Widget Name (English) --> <!-- Widget Name (English) -->
...@@ -19,8 +29,13 @@ ...@@ -19,8 +29,13 @@
<label class="block text-gray-700 text-sm font-bold mb-2" for="engName"> <label class="block text-gray-700 text-sm font-bold mb-2" for="engName">
Widget Name (English) Widget Name (English)
</label> </label>
<input formControlName="engName" id="engName" type="text" placeholder="e.g., Company News" <ejs-textbox
class="shadow-sm border border-gray-200 rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> formControlName="engName"
id="engName"
placeholder="e.g., Company News"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-textbox>
</div> </div>
<!-- Component Name --> <!-- Component Name -->
...@@ -28,8 +43,15 @@ ...@@ -28,8 +43,15 @@
<label class="block text-gray-700 text-sm font-bold mb-2" for="component"> <label class="block text-gray-700 text-sm font-bold mb-2" for="component">
Angular Component Name Angular Component Name
</label> </label>
<input formControlName="component" id="component" type="text" placeholder="e.g., CompanyNewsComponent" <ejs-dropdownlist
class="shadow-sm border border-gray-200 rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> formControlName="component"
id="component"
[dataSource]="componentData"
[fields]="componentFields"
placeholder="e.g., CompanyNewsComponent"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-dropdownlist>
</div> </div>
<!-- Sizing --> <!-- Sizing -->
...@@ -38,37 +60,58 @@ ...@@ -38,37 +60,58 @@
<label class="block text-gray-700 text-sm font-bold mb-2" for="cols"> <label class="block text-gray-700 text-sm font-bold mb-2" for="cols">
Default Columns Default Columns
</label> </label>
<input formControlName="cols" id="cols" type="number" min="1" <ejs-numerictextbox
class="shadow-sm border border-gray-200 rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> formControlName="cols"
id="cols"
[min]="1"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-numerictextbox>
</div> </div>
<div class="w-full md:w-1/2 px-3"> <div class="w-full md:w-1/2 px-3">
<label class="block text-gray-700 text-sm font-bold mb-2" for="rows"> <label class="block text-gray-700 text-sm font-bold mb-2" for="rows">
Default Rows Default Rows
</label> </label>
<input formControlName="rows" id="rows" type="number" min="1" <ejs-numerictextbox
class="shadow-sm border border-gray-200 rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-transparent"> formControlName="rows"
id="rows"
[min]="1"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-numerictextbox>
</div> </div>
</div> </div>
<!-- Preview Section --> <!-- Preview Section -->
<div class="mb-6 p-6 border border-gray-200 rounded-lg bg-gray-50"> <div class="mb-6 p-6 border border-gray-200 rounded-lg bg-gray-50">
<h3 class="text-xl font-bold mb-4 text-gray-800">Widget Preview</h3> <h3 class="text-xl font-bold mb-4 text-gray-800">Widget Preview</h3>
<div class="w-full h-48 border border-gray-300 rounded-lg overflow-hidden shadow-inner flex items-center justify-center bg-white"> <div
class="w-full h-48 border border-gray-300 rounded-lg overflow-hidden shadow-inner flex items-center justify-center bg-white"
>
<ng-container *ngComponentOutlet="previewComponentType"></ng-container> <ng-container *ngComponentOutlet="previewComponentType"></ng-container>
<p *ngIf="!previewComponentType" class="text-gray-500">Enter a valid component name to see a preview.</p> <p *ngIf="!previewComponentType" class="text-gray-500">
Enter a valid component name to see a preview.
</p>
</div> </div>
</div> </div>
<!-- Action Buttons --> <!-- Action Buttons -->
<div class="flex items-center justify-end mt-8"> <div class="flex items-center justify-end mt-8">
<button (click)="cancel()" type="button" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-lg focus:outline-none focus:shadow-outline mr-3 transition duration-150 ease-in-out"> <button
(click)="cancel()"
type="button"
class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold py-2 px-4 rounded-lg focus:outline-none focus:shadow-outline mr-3 transition duration-150 ease-in-out"
>
Cancel Cancel
</button> </button>
<button type="submit" [disabled]="widgetForm.invalid" class="bg-primary hover:bg-primary-dark text-white font-bold py-2 px-4 rounded-lg focus:outline-none focus:shadow-outline disabled:bg-gray-400 disabled:cursor-not-allowed transition duration-150 ease-in-out"> <button
type="submit"
[disabled]="widgetForm.invalid"
class="bg-primary hover:bg-primary-dark text-white font-bold py-2 px-4 rounded-lg focus:outline-none focus:shadow-outline disabled:bg-gray-400 disabled:cursor-not-allowed transition duration-150 ease-in-out"
>
Save Widget Save Widget
</button> </button>
</div> </div>
</form> </form>
</div> </div>
import { Component, OnInit, Type, Inject } from '@angular/core'; import { Component, OnInit, Type, Inject } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import {
FormBuilder,
FormGroup,
ReactiveFormsModule,
Validators,
} from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgComponentOutlet } from '@angular/common'; import { NgComponentOutlet } from '@angular/common';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { TextBoxModule } from '@syncfusion/ej2-angular-inputs';
import { NumericTextBoxModule } from '@syncfusion/ej2-angular-inputs';
import { DropDownListModule } from '@syncfusion/ej2-angular-dropdowns';
// import * as DashboardActions from '../state/dashboard.actions'; // import * as DashboardActions from '../state/dashboard.actions';
import { WidgetModel } from '../models/widgets.model'; import { WidgetModel } from '../models/widgets.model';
...@@ -25,7 +33,26 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg ...@@ -25,7 +33,26 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg
@Component({ @Component({
selector: 'app-widget-form', selector: 'app-widget-form',
standalone: true, standalone: true,
imports: [CommonModule, ReactiveFormsModule, NgComponentOutlet, CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, KpiWidgetComponent, WelcomeWidgetComponent, ChartWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent], imports: [
CommonModule,
ReactiveFormsModule,
NgComponentOutlet,
TextBoxModule,
NumericTextBoxModule,
DropDownListModule,
CompanyInfoWidgetComponent,
HeadcountWidgetComponent,
AttendanceOverviewWidgetComponent,
PayrollSummaryWidgetComponent,
EmployeeDirectoryWidgetComponent,
KpiWidgetComponent,
WelcomeWidgetComponent,
ChartWidgetComponent,
QuickLinksWidgetComponent,
SyncfusionDatagridWidgetComponent,
SyncfusionPivotWidgetComponent,
SyncfusionChartWidgetComponent,
],
templateUrl: './widget-form.component.html', templateUrl: './widget-form.component.html',
}) })
export class WidgetFormComponent implements OnInit { export class WidgetFormComponent implements OnInit {
...@@ -34,34 +61,41 @@ export class WidgetFormComponent implements OnInit { ...@@ -34,34 +61,41 @@ export class WidgetFormComponent implements OnInit {
appName: string = ''; appName: string = '';
previewComponentType: Type<any> | null = null; // For dynamic preview previewComponentType: Type<any> | null = null; // For dynamic preview
public componentData: { [key: string]: string }[] = [];
public componentFields: object = { text: 'name', value: 'id' };
// Map string names to actual component classes // Map string names to actual component classes
private widgetComponentMap: { [key: string]: Type<any> } = { private widgetComponentMap: { [key: string]: Type<any> } = {
'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent, CompanyInfoWidgetComponent: CompanyInfoWidgetComponent,
'HeadcountWidgetComponent': HeadcountWidgetComponent, HeadcountWidgetComponent: HeadcountWidgetComponent,
'AttendanceOverviewWidgetComponent': AttendanceOverviewWidgetComponent, AttendanceOverviewWidgetComponent: AttendanceOverviewWidgetComponent,
'PayrollSummaryWidgetComponent': PayrollSummaryWidgetComponent, PayrollSummaryWidgetComponent: PayrollSummaryWidgetComponent,
'EmployeeDirectoryWidgetComponent': EmployeeDirectoryWidgetComponent, EmployeeDirectoryWidgetComponent: EmployeeDirectoryWidgetComponent,
'KpiWidgetComponent': KpiWidgetComponent, KpiWidgetComponent: KpiWidgetComponent,
'WelcomeWidgetComponent': WelcomeWidgetComponent, WelcomeWidgetComponent: WelcomeWidgetComponent,
'ChartWidgetComponent': ChartWidgetComponent, ChartWidgetComponent: ChartWidgetComponent,
'QuickLinksWidgetComponent': QuickLinksWidgetComponent, QuickLinksWidgetComponent: QuickLinksWidgetComponent,
'SyncfusionDatagridWidgetComponent': SyncfusionDatagridWidgetComponent, SyncfusionDatagridWidgetComponent: SyncfusionDatagridWidgetComponent,
'SyncfusionPivotWidgetComponent': SyncfusionPivotWidgetComponent, SyncfusionPivotWidgetComponent: SyncfusionPivotWidgetComponent,
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent SyncfusionChartWidgetComponent: SyncfusionChartWidgetComponent,
}; };
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
public dialogRef: MatDialogRef<WidgetFormComponent>, public dialogRef: MatDialogRef<WidgetFormComponent>,
@Inject(MAT_DIALOG_DATA) public data: { widget: WidgetModel, isNew: boolean } @Inject(MAT_DIALOG_DATA) public data: { widget: WidgetModel; isNew: boolean }
) { } ) {}
ngOnInit(): void { ngOnInit(): void {
this.isNew = this.data.isNew; this.isNew = this.data.isNew;
this.componentData = Object.keys(this.widgetComponentMap).map((key) => ({
id: key,
name: key,
}));
this.widgetForm = this.fb.group({ this.widgetForm = this.fb.group({
id: [null], widgetId: [this.data.widget?.widgetId || new Date().getTime()],
thName: ['', Validators.required], thName: ['', Validators.required],
engName: ['', Validators.required], engName: ['', Validators.required],
component: ['', Validators.required], component: ['', Validators.required],
...@@ -69,7 +103,7 @@ export class WidgetFormComponent implements OnInit { ...@@ -69,7 +103,7 @@ export class WidgetFormComponent implements OnInit {
rows: [1, [Validators.required, Validators.min(1)]], rows: [1, [Validators.required, Validators.min(1)]],
x: [0], x: [0],
y: [0], y: [0],
data: [null] data: [null],
}); });
if (!this.isNew && this.data.widget) { if (!this.isNew && this.data.widget) {
...@@ -78,7 +112,7 @@ export class WidgetFormComponent implements OnInit { ...@@ -78,7 +112,7 @@ export class WidgetFormComponent implements OnInit {
} }
// Subscribe to component name changes for live preview // Subscribe to component name changes for live preview
this.widgetForm.get('component')?.valueChanges.subscribe(componentName => { this.widgetForm.get('component')?.valueChanges.subscribe((componentName) => {
this.updatePreview(componentName); this.updatePreview(componentName);
}); });
} }
......
<div class="p-4 sm:p-6 lg:p-8"> <div class="p-4 sm:p-6 lg:p-8">
<div class="sm:flex sm:items-center justify-between"> <div class="sm:flex sm:items-center justify-between mb-6">
<div class="sm:flex-auto"> <div class="sm:flex-auto">
<h1 class="text-xl font-semibold text-gray-900">Widget Registry</h1> <h1 class="text-xl font-semibold text-gray-900">Widget Registry</h1>
<p class="mt-2 text-sm text-gray-700"> <p class="mt-2 text-sm text-gray-700">
...@@ -7,123 +7,60 @@ ...@@ -7,123 +7,60 @@
central API. central API.
</p> </p>
</div> </div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none flex space-x-2"> <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<button <button (click)="openAddWidgetDialog()" type="button" class="inline-flex items-center justify-center rounded-md border border-transparent bg-indigo px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo/70 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto">
(click)="openAddWidgetDialog()"
type="button"
class="btn btn-green sm:w-auto"
>
Add New Widget Add New Widget
</button> </button>
<button
routerLink="/portal-manage/widget-management/linker"
type="button"
class="btn btn-indigo sm:w-auto"
>
Manage Dataset Widgets
</button>
</div>
</div>
<!-- Search/Filter Input -->
<div class="mt-6 relative rounded-md shadow-sm">
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<!-- Assuming Boxicons or RemixIcons are available. Using a generic search icon class. -->
<i class="bx bx-search text-gray-400"></i>
</div> </div>
<input
type="text"
[(ngModel)]="searchTerm"
(input)="applyFilter()"
placeholder="Search widgets..."
class="block w-full rounded-md border-gray-300 pl-10 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm p-2"
/>
</div> </div>
<!-- Registered Widgets List --> <ejs-grid
<div class="mt-8 bg-white shadow-lg rounded-lg p-6"> #grid
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8"> [dataSource]="registeredWidgets"
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8"> [allowPaging]="true"
<div [allowSorting]="true"
class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg" [pageSettings]="pageSettings"
> [toolbar]="toolbarOptions"
<table class="min-w-full divide-y divide-gray-300"> (toolbarClick)="toolbarClickHandler($event)"
<thead class="bg-gray-50"> (commandClick)="commandClickHandler($event)"
<tr> [filterSettings]="filterSettings"
<th [allowFiltering]="true"
scope="col" height="600"
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6"
>
Name
</th>
<th
scope="col"
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
> >
Component <e-columns>
</th> <e-column headerText="Name" width="250">
<th <ng-template #template let-data>
scope="col"
class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
>
Default Size
</th>
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
<span class="sr-only">Edit</span>
<span class="sr-only">Delete</span>
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
<tr *ngFor="let widget of filteredWidgets" class="hover:bg-gray-50">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm sm:pl-6">
<div class="flex items-center"> <div class="flex items-center">
<div class="ml-4"> <div class="ml-4">
<div class="font-medium text-gray-900"> <div class="font-medium text-gray-900">
{{ widget.thName }} {{ data.thName }}
</div> </div>
<div class="text-gray-500">{{ widget.engName }}</div> <div class="text-gray-500">{{ data.engName }}</div>
</div> </div>
</div> </div>
</td> </ng-template>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"> </e-column>
<span <e-column
class="inline-flex rounded-full bg-blue/50 px-2 text-xs font-semibold leading-5 text-white" field="component"
>{{ widget.component }}</span headerText="Component"
> width="250"
</td> ></e-column>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500"> <e-column
{{ widget.cols }}x{{ widget.rows }} field="cols"
</td> headerText="Default Size"
<td width="150"
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6" textAlign="Center"
> >
<button <ng-template #template let-data>
(click)="openEditWidgetDialog(widget)" <span>{{ data.cols }}x{{ data.rows }}</span>
class="btn-text-indigo mr-4" </ng-template>
> </e-column>
Edit <e-column
</button> headerText="Actions"
<button width="120"
(click)="confirmDeleteWidget(widget)" [commands]="commands"
class="btn btn-text-red" textAlign="Right"
> ></e-column>
Delete </e-columns>
</button> </ejs-grid>
</td>
</tr>
<tr *ngIf="filteredWidgets.length === 0">
<td
colspan="5"
class="whitespace-nowrap px-3 py-4 text-lg font-semibold text-center text-gray-500"
>
No widgets found.
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div> </div>
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