Commit 9070d968 by Ooh-Ao

datagrid

parent d1e8e954
...@@ -20,22 +20,81 @@ ...@@ -20,22 +20,81 @@
</div> </div>
<!-- Grid --> <!-- Grid -->
<ejs-grid #grid *ngIf="!isLoading && !hasError" <ejs-grid #grid *ngIf="!isLoading && !hasError"
[dataSource]="data" [dataSource]="data"
[allowPaging]="true" [allowPaging]="true"
[pageSettings]="pageSettings" [pageSettings]="pageSettings"
[allowSorting]="true" [allowSorting]="true"
[allowFiltering]="true" [allowFiltering]="true"
[allowGrouping]="true" [allowGrouping]="true"
[toolbar]="toolbar" [toolbar]="toolbar"
(toolbarClick)="toolbarClick($event)" (toolbarClick)="toolbarClick($event)"
[allowExcelExport]="true" [allowExcelExport]="true"
[allowPdfExport]="true" [allowPdfExport]="true"
[searchSettings]="searchSettings"
[groupSettings]="groupSettings"
[filterSettings]="filterSettings"
[editSettings]="editSettings"
[selectionSettings]="selectionOptions"
[loadingIndicator]="loadingIndicator"
[query]="query"
[showColumnMenu]="showColumnMenu"
[allowReordering]="allowReordering"
[allowMultiSorting]="allowMultiSorting"
[columnMenuItems]="columnMenuItems"
(columnMenuClick)="onColumnMenuClick($event)"
(actionComplete)="actionComplete($event)"
height="100%"> height="100%">
<e-columns> <e-columns>
<e-column *ngFor="let col of columns" [field]="col.field" [headerText]="col.headerText" [width]="col.width"></e-column> <e-column *ngFor="let col of columns" [field]="col.field" [headerText]="col.headerText" [width]="col.width"
[format]="col.format" [textAlign]="col.textAlign" [isPrimaryKey]="col.isPrimaryKey" [editType]="col.editType"
[validationRules]="col.validationRules" [allowEditing]="col.allowEditing" [allowSorting]="col.allowSorting"
[allowFiltering]="col.allowFiltering" [visible]="col.visible" [type]="col.type">
<ng-template #headerTemplate let-data>
<span class="font-size-12px font-weight-700">{{ col.headerText }}</span>
</ng-template>
</e-column>
</e-columns> </e-columns>
<e-aggregates>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesSum" [field]="col.field" [type]="'Sum'" [footerTemplate]="'Sum: ${Sum}'"
[groupFooterTemplate]="'Sum: ${Sum}'" [groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesCount" [field]="col.field" [type]="'Count'"
[footerTemplate]="'Count: ${Count}'" [groupFooterTemplate]="'Count: ${Count}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesAvg" [field]="col.field" [type]="'Average'"
[footerTemplate]="'Average: ${Average}'" [groupFooterTemplate]="'Average: ${Average}'"
[groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesMin" [field]="col.field" [type]="'Min'" [footerTemplate]="'Min: ${Min}'"
[groupFooterTemplate]="'Min: ${Min}'" [groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
<e-aggregate>
<e-columns>
<e-column *ngFor="let col of aggregatesMax" [field]="col.field" [type]="'Max'" [footerTemplate]="'Max: ${Max}'"
[groupFooterTemplate]="'Max: ${Max}'" [groupCaptionTemplate]="col.groupCaptionTemplate" [format]="col.format">
</e-column>
</e-columns>
</e-aggregate>
</e-aggregates>
</ejs-grid> </ejs-grid>
</div> </div>
</div> </div>
\ No newline at end of file
import { Component, ViewChild } from '@angular/core'; import { Component, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { GridModule, PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService, GridComponent, ToolbarItems } from '@syncfusion/ej2-angular-grids'; import { GridModule, PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService, GridComponent, ToolbarItems, SearchSettingsModel, GroupSettingsModel, FilterSettingsModel, SelectionSettingsModel, AggregateService, ColumnMenuService, DetailRowService, ReorderService, EditService, ColumnMenuClickEventArgs, PdfExportProperties, ExcelExportProperties, LoadingIndicatorModel, Column,SearchService } from '@syncfusion/ej2-angular-grids';
import { ClickEventArgs } from '@syncfusion/ej2-navigations'; import { ClickEventArgs } from '@syncfusion/ej2-navigations';
import { DataManager } from '@syncfusion/ej2-data'; import { DataManager, Query } from '@syncfusion/ej2-data';
import { DashboardStateService } from '../../services/dashboard-state.service'; import { DashboardStateService } from '../../services/dashboard-state.service';
import { BaseWidgetComponent } from '../base-widget.component'; import { BaseWidgetComponent } from '../base-widget.component';
import { L10n, setCulture } from '@syncfusion/ej2-base'; // For L10n
setCulture('th-TH');
L10n.load({
'en-US': {
'pager': {
'currentPageInfo': '',
'totalItemsInfo': '{1} to {2} of {0}',
}
},
'th-TH': {
'grid': {
'EmptyRecord': 'ไม่มีข้อมูลที่จะแสดง',
'Item': '1 รายการ',
'Items': '{0} รายการ'
},
'pager': {
'All': 'ทั้งหมด',
'pagerAllDropDown': 'รายการ',
'currentPageInfo': '',
'totalItemsInfo': '{0} รายการ',
'totalItemInfo': '{0} รายการ',
'totalRecordsInfo': '{0} รายการ',
'firstPageTooltip': 'หน้าแรก',
'lastPageTooltip': 'หน้าสุดท้าย',
'nextPageTooltip': 'ถัดไป',
'previousPageTooltip': 'ก่อนหน้า',
'nextPagerTooltip': 'ถัดไป',
'previousPagerTooltip': 'ก่อนหน้า',
'pageInput': '{0}',
'page': 'หน้า',
'pagerDropDown': 'รายการ',
'pagerAll': 'ทั้งหมด',
'pageSize': 'รายการต่อหน้า',
'pageSizeAll': 'ทั้งหมด',
'pageCount': 'จำนวนหน้า',
'pageCountAll': 'ทั้งหมด'
}
}
});
@Component({ @Component({
selector: 'app-syncfusion-datagrid-widget', selector: 'app-syncfusion-datagrid-widget',
standalone: true, standalone: true,
imports: [CommonModule, GridModule], imports: [CommonModule, GridModule],
providers: [PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService], providers: [PageService, SortService, FilterService, GroupService, ToolbarService, ExcelExportService, PdfExportService, AggregateService, ColumnMenuService, DetailRowService, ReorderService, EditService, SearchService],
templateUrl: './syncfusion-datagrid-widget.component.html', templateUrl: './syncfusion-datagrid-widget.component.html',
}) })
export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent { export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent {
...@@ -19,7 +60,23 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent { ...@@ -19,7 +60,23 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent {
public data: DataManager = new DataManager([]); public data: DataManager = new DataManager([]);
public columns: any[] = []; public columns: any[] = [];
public pageSettings: Object = { pageSize: 10 }; public pageSettings: Object = { pageSize: 10 };
public toolbar: ToolbarItems[] = ['Search', 'ExcelExport', 'PdfExport', 'CsvExport']; public toolbar: ToolbarItems[]; // Make it configurable
public searchSettings: SearchSettingsModel;
public groupSettings: GroupSettingsModel;
public filterSettings: FilterSettingsModel;
public editSettings: Object;
public selectionOptions: SelectionSettingsModel;
public loadingIndicator: LoadingIndicatorModel;
public query: Query;
public columnMenuItems: any[];
public aggregatesSum: any[] = [];
public aggregatesCount: any[] = [];
public aggregatesAvg: any[] = [];
public aggregatesMin: any[] = [];
public aggregatesMax: any[] = [];
public allowReordering: boolean;
public showColumnMenu: boolean;
public allowMultiSorting: boolean;
constructor(protected override dashboardStateService: DashboardStateService) { constructor(protected override dashboardStateService: DashboardStateService) {
super(dashboardStateService); super(dashboardStateService);
...@@ -29,6 +86,32 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent { ...@@ -29,6 +86,32 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent {
this.title = this.config.title || 'Data Grid'; this.title = this.config.title || 'Data Grid';
this.columns = this.config.columns || []; this.columns = this.config.columns || [];
this.data = new DataManager([]); this.data = new DataManager([]);
this.pageSettings = this.config.pageSettings || { pageSize: 10 };
this.toolbar = this.config.toolbar || ['Search', 'ExcelExport', 'PdfExport', 'CsvExport'];
this.searchSettings = this.config.searchSettings || { fields: [], operator: 'contains', ignoreCase: true };
this.groupSettings = this.config.groupSettings || { allowReordering: true, showGroupedColumn: true, showDropArea: false };
this.filterSettings = this.config.filterSettings || { type: 'Excel' };
this.editSettings = this.config.editSettings || { allowEditing: true, mode: 'Batch' };
this.selectionOptions = this.config.selectionOptions || { checkboxOnly: true };
this.loadingIndicator = this.config.loadingIndicator || { indicatorType: 'Shimmer' };
this.query = this.config.query || new Query().addParams('dataCount', '1000');
this.columnMenuItems = this.config.columnMenuItems || [
'AutoFit', 'AutoFitAll', 'SortAscending', 'SortDescending',
'Group', 'Ungroup', 'ColumnChooser', 'Filter',
{ text: 'Sum', id: 'aggregate_sum' },
{ text: 'Count', id: 'aggregate_count' },
{ text: 'Average', id: 'aggregate_average' },
{ text: 'Min', id: 'aggregate_min' },
{ text: 'Max', id: 'aggregate_max' }
];
this.aggregatesSum = this.config.aggregatesSum || [];
this.aggregatesCount = this.config.aggregatesCount || [];
this.aggregatesAvg = this.config.aggregatesAvg || [];
this.aggregatesMin = this.config.aggregatesMin || [];
this.aggregatesMax = this.config.aggregatesMax || [];
this.allowReordering = this.config.allowReordering !== undefined ? this.config.allowReordering : true;
this.showColumnMenu = this.config.showColumnMenu !== undefined ? this.config.showColumnMenu : true;
this.allowMultiSorting = this.config.allowMultiSorting !== undefined ? this.config.allowMultiSorting : true;
} }
onDataUpdate(data: any[]): void { onDataUpdate(data: any[]): void {
...@@ -48,6 +131,32 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent { ...@@ -48,6 +131,32 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent {
this.title = 'Data Grid (Default)'; this.title = 'Data Grid (Default)';
this.data = new DataManager([]); this.data = new DataManager([]);
this.columns = [{ field: 'Message', headerText: 'Please select a dataset' }]; this.columns = [{ field: 'Message', headerText: 'Please select a dataset' }];
this.pageSettings = { pageSize: 10 };
this.toolbar = ['Search', 'ExcelExport', 'PdfExport', 'CsvExport'];
this.searchSettings = { fields: [], operator: 'contains', ignoreCase: true };
this.groupSettings = { allowReordering: true, showGroupedColumn: true, showDropArea: false };
this.filterSettings = { type: 'Excel' };
this.editSettings = { allowEditing: true, mode: 'Batch' };
this.selectionOptions = { checkboxOnly: true };
this.loadingIndicator = { indicatorType: 'Shimmer' };
this.query = new Query().addParams('dataCount', '1000');
this.columnMenuItems = [
'AutoFit', 'AutoFitAll', 'SortAscending', 'SortDescending',
'Group', 'Ungroup', 'ColumnChooser', 'Filter',
{ text: 'Sum', id: 'aggregate_sum' },
{ text: 'Count', id: 'aggregate_count' },
{ text: 'Average', id: 'aggregate_average' },
{ text: 'Min', id: 'aggregate_min' },
{ text: 'Max', id: 'aggregate_max' }
];
this.aggregatesSum = [];
this.aggregatesCount = [];
this.aggregatesAvg = [];
this.aggregatesMin = [];
this.aggregatesMax = [];
this.allowReordering = true;
this.showColumnMenu = true;
this.allowMultiSorting = true;
} }
toolbarClick(args: ClickEventArgs): void { toolbarClick(args: ClickEventArgs): void {
...@@ -55,11 +164,94 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent { ...@@ -55,11 +164,94 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent {
const fileName = `${this.title}.xlsx`; const fileName = `${this.title}.xlsx`;
if (args.item.id.includes('_excelexport')) { if (args.item.id.includes('_excelexport')) {
this.grid.excelExport({ fileName }); let exportProperties: ExcelExportProperties = {
columns: this.columns.map(col => ({
field: col.field,
headerText: col.headerText
})) as Column[]
};
this.grid.excelExport(exportProperties);
} else if (args.item.id.includes('_pdfexport')) { } else if (args.item.id.includes('_pdfexport')) {
this.grid.pdfExport({ fileName: `${this.title}.pdf` }); this.grid.pdfExport({ fileName: `${this.title}.pdf` });
} else if (args.item.id.includes('_csvexport')) { } else if (args.item.id.includes('_csvexport')) {
this.grid.csvExport({ fileName: `${this.title}.csv` }); let exportColumns = this.columns.map(col => ({
field: col.field!,
headerText: col.headerText!
}));
this.grid.csvExport({ columns: exportColumns as Column[] });
} else if (args.item.id.includes('_print')) {
this.grid.print();
}
}
actionComplete(args: any) {
// This method is typically used for client-side filtering/searching
// If the grid is handling filtering/searching internally, this might not be needed
// or can be used for custom logic.
}
onColumnMenuClick(args: ColumnMenuClickEventArgs): void {
if (!args.item.id) { return; }
if (args.item.id.startsWith('aggregate_')) {
const colField = (args.column as any)?.field;
if (!colField) { return; }
const selectedAgg = args.item.id.split('_')[1];
if (selectedAgg === 'sum') {
if (this.aggregatesSum.find(a => a.field === colField)) {
this.aggregatesSum = this.aggregatesSum.filter(a => a.field !== colField);
} else {
this.aggregatesSum.push({
field: colField,
type: 'Sum',
footerTemplate: 'Sum: ${Sum}'
});
}
} else if (selectedAgg === 'count') {
if (this.aggregatesCount.find(a => a.field === colField)) {
this.aggregatesCount = this.aggregatesCount.filter(a => a.field !== colField);
} else {
this.aggregatesCount.push({
field: colField,
type: 'Count',
footerTemplate: 'Count: ${Count}'
});
}
} else if (selectedAgg === 'average') {
if (this.aggregatesAvg.find(a => a.field === colField)) {
this.aggregatesAvg = this.aggregatesAvg.filter(a => a.field !== colField);
} else {
this.aggregatesAvg.push({
field: colField,
type: 'Average',
footerTemplate: 'Avg: ${Average}'
});
}
} else if (selectedAgg === 'min') {
if (this.aggregatesMin.find(a => a.field === colField)) {
this.aggregatesMin = this.aggregatesMin.filter(a => a.field !== colField);
} else {
this.aggregatesMin.push({
field: colField,
type: 'Min',
footerTemplate: 'Min: ${Min}'
});
}
} else if (selectedAgg === 'max') {
if (this.aggregatesMax.find(a => a.field === colField)) {
this.aggregatesMax = this.aggregatesMax.filter(a => a.field !== colField);
} else {
this.aggregatesMax.push({
field: colField,
type: 'Max',
footerTemplate: 'Max: ${Max}'
});
}
}
this.grid?.refreshColumns(); // Refresh columns to show/hide aggregates
this.grid?.refresh(); // Refresh grid to recalculate aggregates
} }
} }
......
...@@ -59,3 +59,53 @@ ...@@ -59,3 +59,53 @@
#search-modal ul li { #search-modal ul li {
@apply cursor-pointer; @apply cursor-pointer;
} }
// th{
// position: relative; // เทียบเท่า class "relative"
// padding: 10px; // เทียบเท่า class "px-10px py-10px" (อาจเปลี่ยนตามต้องการ)
// background-color: rgb(96 165 250 / 0.1); // ตัวอย่างแทน "bg-soft-secondary"
// color: #2b2b2b; // ตัวอย่างแทน "text-primary"
// text-align: center !important; // เทียบเท่า "!text-center"
// // หากต้องการดีไซน์อื่น ๆ เพิ่มเติมก็ใส่ในนี้ได้เลย เช่น:
// font-weight: 600;
// border-bottom: 1px solid #eee;
// }
.e-headercell,
.e-detailheadercell {
background-color: rgb(96 165 250 / 0.1) !important;
}
.e-pager .e-currentitem, .e-pager .e-currentitem:hover {
background: rgb(96 165 250) !important;
color: #fff;
opacity: 1 !important;
}
.e-checkbox-wrapper .e-frame.e-check, .e-css.e-checkbox-wrapper .e-frame.e-check {
background-color: rgb(96 165 250) !important;
border-color: transparent;
color: #fff;
}
.e-checkbox-wrapper .e-frame, .e-css.e-checkbox-wrapper .e-frame {
border: 1px solid !important;
border-radius: 2px;
box-sizing: border-box;
cursor: pointer;
display: inline-block;
font-family: "e-icons";
height: 18px;
line-height: 10px;
padding: 2px 0;
text-align: center;
vertical-align: middle;
width: 1rem !important;
border-color: #64748b !important;
}
.e-grid td.e-selectionbackground {
background-color: #aec2ec !important;
}
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