Commit e1eb60f1 by Ooh-Ao

manage widget

parent b2633955
......@@ -42,6 +42,7 @@
"@syncfusion/ej2-angular-inputs": "^29.2.4",
"@syncfusion/ej2-angular-layouts": "^29.2.4",
"@syncfusion/ej2-angular-maps": "^29.1.33",
"@syncfusion/ej2-angular-navigations": "^29.2.4",
"@syncfusion/ej2-angular-pivotview": "^29.2.4",
"@syncfusion/ej2-angular-popups": "^29.2.8",
"@syncfusion/ej2-angular-treemap": "^29.2.4",
......@@ -6316,6 +6317,31 @@
"@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": {
"version": "29.2.10",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-angular-pivotview/-/ej2-angular-pivotview-29.2.10.tgz",
......
......@@ -27,7 +27,7 @@ export class WidgetService {
downloadFile(logId: string): Observable<any> {
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
return this.http.post(this.url + 'widget-registry', body)
}
......
<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>
<form [formGroup]="widgetForm" (ngSubmit)="onSubmit()" class="bg-white shadow-xl rounded-xl px-8 pt-8 pb-8 mb-4">
<h1 class="text-3xl font-extrabold mb-6 text-gray-800">
{{ isNew ? 'Add New Widget' : 'Edit Widget' }}
</h1>
<form
[formGroup]="widgetForm"
(ngSubmit)="onSubmit()"
class="bg-white shadow-xl rounded-xl px-8 pt-8 pb-8 mb-4"
>
<!-- Widget Name (Thai) -->
<div class="mb-6">
<label class="block text-gray-700 text-sm font-bold mb-2" for="thName">
Widget Name (Thai)
</label>
<input formControlName="thName" id="thName" type="text" placeholder="เช่น ข่าวสารบริษัท"
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">
<ejs-textbox
formControlName="thName"
id="thName"
placeholder="เช่น ข่าวสารบริษัท"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-textbox>
</div>
<!-- Widget Name (English) -->
......@@ -19,8 +29,13 @@
<label class="block text-gray-700 text-sm font-bold mb-2" for="engName">
Widget Name (English)
</label>
<input formControlName="engName" id="engName" type="text" placeholder="e.g., Company News"
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">
<ejs-textbox
formControlName="engName"
id="engName"
placeholder="e.g., Company News"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-textbox>
</div>
<!-- Component Name -->
......@@ -28,8 +43,15 @@
<label class="block text-gray-700 text-sm font-bold mb-2" for="component">
Angular Component Name
</label>
<input formControlName="component" id="component" type="text" placeholder="e.g., CompanyNewsComponent"
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">
<ejs-dropdownlist
formControlName="component"
id="component"
[dataSource]="componentData"
[fields]="componentFields"
placeholder="e.g., CompanyNewsComponent"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-dropdownlist>
</div>
<!-- Sizing -->
......@@ -38,37 +60,58 @@
<label class="block text-gray-700 text-sm font-bold mb-2" for="cols">
Default Columns
</label>
<input formControlName="cols" id="cols" type="number" min="1"
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">
<ejs-numerictextbox
formControlName="cols"
id="cols"
[min]="1"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-numerictextbox>
</div>
<div class="w-full md:w-1/2 px-3">
<label class="block text-gray-700 text-sm font-bold mb-2" for="rows">
Default Rows
</label>
<input formControlName="rows" id="rows" type="number" min="1"
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">
<ejs-numerictextbox
formControlName="rows"
id="rows"
[min]="1"
cssClass="e-outline"
floatLabelType="Auto"
></ejs-numerictextbox>
</div>
</div>
<!-- Preview Section -->
<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>
<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>
<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>
<!-- Action Buttons -->
<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
</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
</button>
</div>
</form>
</div>
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 { NgComponentOutlet } from '@angular/common';
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 { WidgetModel } from '../models/widgets.model';
......@@ -25,7 +33,26 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg
@Component({
selector: 'app-widget-form',
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',
})
export class WidgetFormComponent implements OnInit {
......@@ -34,34 +61,41 @@ export class WidgetFormComponent implements OnInit {
appName: string = '';
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
private widgetComponentMap: { [key: string]: Type<any> } = {
'CompanyInfoWidgetComponent': CompanyInfoWidgetComponent,
'HeadcountWidgetComponent': HeadcountWidgetComponent,
'AttendanceOverviewWidgetComponent': AttendanceOverviewWidgetComponent,
'PayrollSummaryWidgetComponent': PayrollSummaryWidgetComponent,
'EmployeeDirectoryWidgetComponent': EmployeeDirectoryWidgetComponent,
'KpiWidgetComponent': KpiWidgetComponent,
'WelcomeWidgetComponent': WelcomeWidgetComponent,
'ChartWidgetComponent': ChartWidgetComponent,
'QuickLinksWidgetComponent': QuickLinksWidgetComponent,
'SyncfusionDatagridWidgetComponent': SyncfusionDatagridWidgetComponent,
'SyncfusionPivotWidgetComponent': SyncfusionPivotWidgetComponent,
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent
CompanyInfoWidgetComponent: CompanyInfoWidgetComponent,
HeadcountWidgetComponent: HeadcountWidgetComponent,
AttendanceOverviewWidgetComponent: AttendanceOverviewWidgetComponent,
PayrollSummaryWidgetComponent: PayrollSummaryWidgetComponent,
EmployeeDirectoryWidgetComponent: EmployeeDirectoryWidgetComponent,
KpiWidgetComponent: KpiWidgetComponent,
WelcomeWidgetComponent: WelcomeWidgetComponent,
ChartWidgetComponent: ChartWidgetComponent,
QuickLinksWidgetComponent: QuickLinksWidgetComponent,
SyncfusionDatagridWidgetComponent: SyncfusionDatagridWidgetComponent,
SyncfusionPivotWidgetComponent: SyncfusionPivotWidgetComponent,
SyncfusionChartWidgetComponent: SyncfusionChartWidgetComponent,
};
constructor(
private fb: FormBuilder,
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 {
this.isNew = this.data.isNew;
this.componentData = Object.keys(this.widgetComponentMap).map((key) => ({
id: key,
name: key,
}));
this.widgetForm = this.fb.group({
id: [null],
widgetId: [this.data.widget?.widgetId || new Date().getTime()],
thName: ['', Validators.required],
engName: ['', Validators.required],
component: ['', Validators.required],
......@@ -69,7 +103,7 @@ export class WidgetFormComponent implements OnInit {
rows: [1, [Validators.required, Validators.min(1)]],
x: [0],
y: [0],
data: [null]
data: [null],
});
if (!this.isNew && this.data.widget) {
......@@ -78,7 +112,7 @@ export class WidgetFormComponent implements OnInit {
}
// 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);
});
}
......
<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">
<h1 class="text-xl font-semibold text-gray-900">Widget Registry</h1>
<p class="mt-2 text-sm text-gray-700">
......@@ -7,123 +7,60 @@
central API.
</p>
</div>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none flex space-x-2">
<button
(click)="openAddWidgetDialog()"
type="button"
class="btn btn-green sm:w-auto"
>
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
<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">
Add New Widget
</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>
<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>
<!-- Registered Widgets List -->
<div class="mt-8 bg-white shadow-lg rounded-lg p-6">
<div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
<div
class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg"
>
<table class="min-w-full divide-y divide-gray-300">
<thead class="bg-gray-50">
<tr>
<th
scope="col"
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
</th>
<th
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="ml-4">
<div class="font-medium text-gray-900">
{{ widget.thName }}
</div>
<div class="text-gray-500">{{ widget.engName }}</div>
</div>
</div>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
<span
class="inline-flex rounded-full bg-blue/50 px-2 text-xs font-semibold leading-5 text-white"
>{{ widget.component }}</span
>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
{{ widget.cols }}x{{ widget.rows }}
</td>
<td
class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6"
>
<button
(click)="openEditWidgetDialog(widget)"
class="btn-text-indigo mr-4"
>
Edit
</button>
<button
(click)="confirmDeleteWidget(widget)"
class="btn btn-text-red"
>
Delete
</button>
</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>
<ejs-grid
#grid
[dataSource]="registeredWidgets"
[allowPaging]="true"
[allowSorting]="true"
[pageSettings]="pageSettings"
[toolbar]="toolbarOptions"
(toolbarClick)="toolbarClickHandler($event)"
(commandClick)="commandClickHandler($event)"
[filterSettings]="filterSettings"
[allowFiltering]="true"
height="600"
>
<e-columns>
<e-column headerText="Name" width="250">
<ng-template #template let-data>
<div class="flex items-center">
<div class="ml-4">
<div class="font-medium text-gray-900">
{{ data.thName }}
</div>
<div class="text-gray-500">{{ data.engName }}</div>
</div>
</div>
</ng-template>
</e-column>
<e-column
field="component"
headerText="Component"
width="250"
></e-column>
<e-column
field="cols"
headerText="Default Size"
width="150"
textAlign="Center"
>
<ng-template #template let-data>
<span>{{ data.cols }}x{{ data.rows }}</span>
</ng-template>
</e-column>
<e-column
headerText="Actions"
width="120"
[commands]="commands"
textAlign="Right"
></e-column>
</e-columns>
</ejs-grid>
</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