Commit ed89bf27 by sawit

แก้ไข widget

parent aac8ace4
......@@ -135,7 +135,7 @@
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border: 1px solid #e5e7eb;
min-height: 600px;
min-height: 1200px;
}
/* Empty State */
......@@ -144,7 +144,7 @@
flex-direction: column;
justify-content: center;
align-items: center;
height: 600px;
height: 1200px;
text-align: center;
color: #6b7280;
}
......@@ -437,39 +437,39 @@
/* Ensure widgets don't exceed panel boundaries and fill full space */
.e-panel .e-panel-container .content > * {
max-width: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: 100% !important;
overflow: hidden !important;
box-sizing: border-box !important;
flex: 1 !important;
display: flex !important;
flex-direction: column !important;
max-width: 100% !important;
min-width: 0 !important;
}
/* Specific widget component styling */
.e-panel .e-panel-container .content > * {
/* Specific styling for Syncfusion Datagrid Widget */
.e-panel .e-panel-container .content app-syncfusion-datagrid-widget {
width: 100% !important;
height: 100% !important;
flex: 1 !important;
display: flex !important;
flex-direction: column !important;
min-height: 0 !important;
}
/* Ensure all child elements fill the space */
.e-panel .e-panel-container .content > * > * {
width: 100% !important;
height: 100% !important;
flex: 1 !important;
min-height: 0 !important;
margin: 0 !important;
padding: 0 !important;
}
/* Force all nested elements to use full space */
.e-panel .e-panel-container .content * {
box-sizing: border-box !important;
}
/* Remove margin/padding from grid container and grid */
.e-panel .e-panel-container .content app-syncfusion-datagrid-widget .grid-container {
margin: 0 !important;
padding: 0 !important;
}
.e-panel .e-panel-container .content app-syncfusion-datagrid-widget .grid-container .ejs-grid {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
max-width: 100% !important;
}
}
}
......
......@@ -486,6 +486,12 @@ export class DashboardManagementComponent implements OnInit, OnDestroy {
originalWidget: widget
};
// Force SyncfusionDatagridWidgetComponent to use full width
if (widget.component === 'SyncfusionDatagridWidgetComponent') {
panel.sizeX = this.columns;
panel.maxSizeX = this.columns;
}
return panel;
})
......
......@@ -163,7 +163,7 @@ export class DashboardViewerComponent implements OnInit, OnDestroy {
return null;
}
return {
const panel = {
id: `${(widget as any).instanceId}-${widget.y}-${widget.x}`,
header: widget.thName,
sizeX: widget.cols || 4, // Default size if not specified
......@@ -177,6 +177,13 @@ export class DashboardViewerComponent implements OnInit, OnDestroy {
data: JSON.stringify(dataObject)
},
};
// Force SyncfusionDatagridWidgetComponent to use full width in viewer
if (widget.component === 'SyncfusionDatagridWidgetComponent') {
panel.sizeX = this.columns;
}
return panel;
})
.filter(panel => panel !== null) as DashboardPanel[];
}
......
......@@ -609,6 +609,10 @@
<div *ngIf="widgetType === 'PieChartWidgetComponent' || widgetType === 'BarChartWidgetComponent' || widgetType === 'AreaChartWidgetComponent' || widgetType === 'DoughnutChartWidgetComponent' || widgetType === 'FunnelChartWidgetComponent'">
<mat-form-field appearance="fill">
<mat-label>Title</mat-label>
<input matInput [(ngModel)]="currentConfig.title" name="title">
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>X-Axis Field</mat-label>
<mat-select [(ngModel)]="currentConfig.xField" name="xField">
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
......@@ -620,18 +624,26 @@
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" *ngIf="widgetType === 'PieChartWidgetComponent' || widgetType === 'DoughnutChartWidgetComponent' || widgetType === 'FunnelChartWidgetComponent'">
<mat-label>Label Field</mat-label>
<mat-select [(ngModel)]="currentConfig.labelField" name="labelField">
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" *ngIf="widgetType === 'PieChartWidgetComponent' || widgetType === 'DoughnutChartWidgetComponent' || widgetType === 'FunnelChartWidgetComponent'">
<mat-label>Value Field</mat-label>
<mat-select [(ngModel)]="currentConfig.valueField" name="valueField">
<mat-option *ngFor="let col of availableColumns" [value]="col">{{ col }}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Aggregation (for Pie/Doughnut/Funnel)</mat-label>
<mat-label>Aggregation</mat-label>
<mat-select [(ngModel)]="currentConfig.aggregation" name="aggregation">
<mat-option value="none">None</mat-option>
<mat-option value="count">Count</mat-option>
<mat-option value="sum">Sum</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Title</mat-label>
<input matInput [(ngModel)]="currentConfig.title" name="title">
</mat-form-field>
</div>
<div *ngIf="widgetType === 'GaugeChartWidgetComponent'">
......
......@@ -218,9 +218,11 @@ export class WidgetConfigComponent implements OnInit, AfterViewInit, OnDestroy {
if (!this.currentConfig.clickAction) this.currentConfig.clickAction = 'none';
// Layout configuration defaults
if (this.currentConfig.width === undefined) this.currentConfig.width = 600;
if (this.currentConfig.width === undefined) this.currentConfig.width = 100;
if (this.currentConfig.height === undefined) this.currentConfig.height = 400;
if (this.currentConfig.responsive === undefined) this.currentConfig.responsive = true;
if (this.currentConfig.widthUnit === undefined) this.currentConfig.widthUnit = '%';
if (this.currentConfig.fullWidth === undefined) this.currentConfig.fullWidth = true;
// Data configuration defaults
if (!this.currentConfig.dataSource) this.currentConfig.dataSource = 'static';
......@@ -257,6 +259,7 @@ export class WidgetConfigComponent implements OnInit, AfterViewInit, OnDestroy {
if (!this.currentConfig.yAxisField) this.currentConfig.yAxisField = '';
if (!this.currentConfig.valueField) this.currentConfig.valueField = '';
if (!this.currentConfig.labelField) this.currentConfig.labelField = '';
if (!this.currentConfig.aggregation) this.currentConfig.aggregation = 'count';
if (!this.currentConfig.apiEndpoint) this.currentConfig.apiEndpoint = '';
// Chart options defaults
......
......@@ -31,20 +31,76 @@ export class BarChartWidgetComponent extends BaseWidgetComponent {
}
onDataUpdate(data: any[]): void {
if (!data || data.length === 0) {
// Check if data is an array
if (!Array.isArray(data) || data.length === 0) {
this.chartData = [];
return;
}
const xField = this.configObj.xField || 'x';
const yField = this.configObj.yField || 'y';
// Support multiple field name formats from config
const xField = this.configObj.xField || this.configObj.xAxisField || 'x';
const yField = this.configObj.yField || this.configObj.yAxisField || 'y';
const valueField = this.configObj.valueField || this.configObj.aggregation ? this.configObj.valueField : 'y';
console.log('BarChart onDataUpdate:', {
data: data.slice(0, 3),
xField,
yField,
valueField,
aggregation: this.configObj.aggregation,
config: this.configObj
});
// Handle aggregation if needed
// Auto-detect if using ID field
let effectiveAggregation = this.configObj.aggregation;
if (this.configObj.aggregation === 'sum' && valueField) {
const fieldLower = valueField.toLowerCase();
if (fieldLower.includes('id') || (fieldLower.includes('employee') && fieldLower.includes('id'))) {
effectiveAggregation = 'count';
console.warn(`BarChart: Detected ID field "${valueField}", switching from sum to count`);
}
}
if (effectiveAggregation === 'sum' || effectiveAggregation === 'count') {
const groupedData = data.reduce((acc, item) => {
const key = item[xField] || '';
if (!acc[key]) {
acc[key] = 0;
}
if (effectiveAggregation === 'count') {
// Count: เพียงแค่นับจำนวน record
acc[key] += 1;
} else if (effectiveAggregation === 'sum' && valueField && item[valueField]) {
// Sum: ใช้ valueField ในการรวมค่า (เช่น salary, amount)
const value = typeof item[valueField] === 'number' ? item[valueField] : parseFloat(item[valueField]) || 0;
acc[key] += value;
} else {
// ถ้าไม่มี valueField หรือไม่ใช่ number ให้ใช้ count แทน
acc[key] += 1;
}
return acc;
}, {});
this.chartData = Object.keys(groupedData).map(key => ({
x: key,
y: groupedData[key]
}));
} else {
// No aggregation - map directly
const fieldToUse = valueField || yField;
this.chartData = data.map(item => ({
x: item[xField] || '',
y: item[yField] || 0
y: item[fieldToUse] || 0
}));
}
console.log('BarChart chartData:', this.chartData.slice(0, 5));
}
onReset(): void {
this.title = 'Bar Chart (Default)';
this.chartData = [
......
......@@ -35,22 +35,34 @@ export abstract class BaseWidgetComponent implements OnInit, OnDestroy {
next: (selectedDataset: SelectedDataset | null) => {
this.isLoading = true;
this.hasError = false;
if (selectedDataset && selectedDataset.data) {
console.log('BaseWidget received dataset:', {
selectedDataset,
hasData: selectedDataset?.data?.length,
widgetConfig: this.configObj
});
if (selectedDataset && selectedDataset.data && Array.isArray(selectedDataset.data)) {
try {
this.originalData = selectedDataset.data;
console.log('BaseWidget applying filters on data of length:', this.originalData.length);
this.applyFilters();
console.log('BaseWidget filtered data length:', this.filteredData.length);
this.onDataUpdate(this.filteredData);
this.isLoading = false;
} catch (error) {
console.error('BaseWidget error in data processing:', error);
this.handleError(error);
}
} else {
console.warn('BaseWidget no dataset available, showing loading state');
// If no dataset is selected, just keep showing the initial config with a loading state.
// The initial state is set by applyInitialConfig().
this.isLoading = true;
}
},
error: (err) => {
console.error('BaseWidget error in subscription:', err);
this.handleError(err);
}
});
......@@ -105,7 +117,9 @@ export abstract class BaseWidgetComponent implements OnInit, OnDestroy {
// Parse data string to array
try {
this.originalData = JSON.parse(this.data);
const parsedData = JSON.parse(this.data);
// Ensure parsed data is an array, not an object
this.originalData = Array.isArray(parsedData) ? parsedData : [];
} catch (error) {
console.warn('Failed to parse data JSON:', error);
this.originalData = [];
......
......@@ -21,33 +21,102 @@ export class PieChartWidgetComponent extends BaseWidgetComponent {
applyInitialConfig(): void {
this.title = this.configObj.title || 'Pie Chart';
this.legendSettings = { visible: true };
this.legendSettings = {
visible: true,
position: 'Bottom',
textStyle: { size: '14px' },
height: '50px',
width: '100%',
alignment: 'Center'
};
this.chartData = [];
}
onDataUpdate(data: any[]): void {
if (!data || data.length === 0) {
// Check if data is an array
if (!Array.isArray(data) || data.length === 0) {
this.chartData = [];
return;
}
const xField = this.configObj.xField || 'x';
const yField = this.configObj.yField || 'y';
// Support multiple field name formats from config
const labelField = this.configObj.labelField || this.configObj.xField || this.configObj.xAxisField || 'x';
const valueField = this.configObj.valueField || this.configObj.yField || this.configObj.yAxisField || 'y';
console.log('PieChart onDataUpdate:', {
title: this.title,
dataLength: data.length,
labelField,
valueField,
aggregation: this.configObj.aggregation,
sampleData: data.slice(0, 2)
});
let transformedData = data;
if (this.configObj.aggregation === 'count') {
// Handle aggregation if needed
// Default to count if using any id field as value
let effectiveAggregation = this.configObj.aggregation;
if (this.configObj.aggregation === 'sum' && valueField) {
const fieldLower = valueField.toLowerCase();
if (fieldLower.includes('id') ||
fieldLower.includes('employee') && fieldLower.includes('id')) {
effectiveAggregation = 'count';
console.warn(`Detected ID field "${valueField}", switching from sum to count aggregation`);
}
}
console.log('Effective aggregation:', effectiveAggregation, 'original:', this.configObj.aggregation, 'labelField:', labelField, 'valueField:', valueField);
if (effectiveAggregation === 'count' || (effectiveAggregation === 'sum' && !valueField)) {
// Count: นับจำนวนรายการตาม labelField
// หรือ Sum ถ้าไม่ระบุ valueField (คล้าย count)
const counts = transformedData.reduce((acc, item) => {
const key = item[xField] || '';
const key = item[labelField] || '';
acc[key] = (acc[key] || 0) + 1;
return acc;
}, {});
transformedData = Object.keys(counts).map(key => ({ x: key, y: counts[key] }));
transformedData = Object.keys(counts).map(key => ({
x: key || '(Empty)',
y: counts[key],
text: `${key || '(Empty)'}: ${counts[key]}`
}));
console.log('PieChart using COUNT aggregation, result:', transformedData);
} else if (effectiveAggregation === 'sum' && valueField) {
// Sum: รวมค่า valueField ตาม labelField (เฉพาะกรณีที่ระบุ valueField)
// ตรวจสอบว่า valueField มีค่าที่สมเหตุสมผลหรือไม่
const groupedData = transformedData.reduce((acc, item) => {
const key = item[labelField] || '';
const value = item[valueField] || 0;
if (!acc[key]) {
acc[key] = 0;
}
// ถ้า value เป็น ID หรือ string ที่ไม่ได้เป็นตัวเลข ให้ไม่ sum
if (typeof value === 'string' && isNaN(Number(value))) {
// ถ้าเป็น ID หรือ string ธรรมดา ให้ข้าม
return acc;
}
acc[key] += typeof value === 'number' ? value : parseFloat(value) || 0;
return acc;
}, {});
transformedData = Object.keys(groupedData).map(key => ({
x: key || '(Empty)',
y: groupedData[key],
text: `${key || '(Empty)'}: ${groupedData[key]}`
}));
} else {
// No aggregation: map directly
transformedData = transformedData.map(item => ({
x: item[xField] || '',
y: item[yField] || 0
x: item[labelField] || '(Empty)',
y: item[valueField] || 0,
text: `${item[labelField] || '(Empty)'}: ${item[valueField] || 0}`
}));
}
console.log('PieChart chartData:', transformedData.slice(0, 10));
this.chartData = transformedData;
}
......
......@@ -325,17 +325,37 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
return;
}
console.log('SimpleKpiWidget onDataUpdate:', {
title: this.title,
dataLength: data?.length,
hasFilter: this.enableFilter,
filterField: this.filterField,
filterValue: this.filterValue
});
// Transform data if transform function is provided
let transformedData = this.transformData(data);
// Apply filtering if enabled
if (this.enableFilter && this.filterField && this.filterValue) {
transformedData = this.applyFilter(transformedData);
console.log('SimpleKpiWidget after filter:', {
filteredLength: transformedData?.length
});
}
// Handle count aggregation separately as it doesn't need a valueField
if (this.aggregation === 'count') {
// If valueField is specified and it's a date/unique field, count unique values
if (this.valueField && ['dateid', 'date', 'datetime', 'day'].some(part => this.valueField.toLowerCase().includes(part))) {
// Count unique values instead of all records
const uniqueValues = new Set(transformedData.map(item => item[this.valueField]));
this.value = uniqueValues.size.toLocaleString();
console.log(`SimpleKpiWidget counting unique ${this.valueField}: ${uniqueValues.size} from ${transformedData.length} records`);
} else {
// Regular count: count all records
this.value = (transformedData?.length || 0).toLocaleString();
}
this.updateTrendData(transformedData);
this.updateLabel(transformedData);
return;
......@@ -914,15 +934,22 @@ export class SimpleKpiWidgetComponent extends BaseWidgetComponent {
return data;
}
console.log('SimpleKpiWidget applying filter:', {
filterField: this.filterField,
filterValue: this.filterValue,
filterOperator: this.filterOperator,
dataLength: data?.length
});
return data.filter(item => {
const fieldValue = item[this.filterField];
const filterValue = this.filterValue;
switch (this.filterOperator) {
case 'equals':
return fieldValue == filterValue;
return String(fieldValue).toLowerCase() === String(filterValue).toLowerCase();
case 'not_equals':
return fieldValue != filterValue;
return String(fieldValue).toLowerCase() !== String(filterValue).toLowerCase();
case 'greater_than':
return Number(fieldValue) > Number(filterValue);
case 'less_than':
......
......@@ -115,6 +115,7 @@
(actionComplete)="actionComplete($event)"
(dataBound)="onDataBound($event)"
[height]="'100%'"
[width]="'100%'"
[allowResizing]="allowResizing"
[rowHeight]="rowHeight">
......
......@@ -5,6 +5,9 @@
height: 100%;
position: relative;
overflow: hidden;
width: 100%;
margin: 0;
padding: 0;
// Widget Header
.widget-header {
......@@ -300,6 +303,10 @@
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
// Interaction States
......@@ -454,10 +461,18 @@
.e-grid {
border: none !important;
font-family: inherit;
width: 100% !important;
height: 100% !important;
min-width: 0 !important;
min-height: 0 !important;
margin: 0 !important;
padding: 0 !important;
max-width: 100% !important;
.e-gridheader {
background-color: inherit;
border-bottom: 1px solid rgba(229, 231, 235, 0.5);
width: 100% !important;
.e-headercell {
background-color: inherit;
......@@ -466,6 +481,10 @@
font-size: 13px;
padding: 12px 8px;
border-right: 1px solid rgba(229, 231, 235, 0.3);
min-width: 0 !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
&:last-child {
border-right: none;
......@@ -475,6 +494,8 @@
.e-content {
background-color: transparent;
width: 100% !important;
min-width: 0 !important;
.e-row {
border-bottom: 1px solid rgba(229, 231, 235, 0.2);
......@@ -497,6 +518,10 @@
border-right: 1px solid rgba(229, 231, 235, 0.2);
font-size: 13px;
color: inherit;
min-width: 0 !important;
overflow: hidden !important;
text-overflow: ellipsis !important;
white-space: nowrap !important;
&:last-child {
border-right: none;
......
......@@ -167,7 +167,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
public animateConditionalChange: boolean = true;
// Layout properties
public width: number = 600;
public width: number = 100;
public height: number = 400;
public minWidth: number = 400;
public minHeight: number = 300;
......@@ -175,11 +175,11 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
public maxHeight: number = 800;
public aspectRatio: string = 'auto';
public responsive: boolean = true;
public widthUnit: string = 'px';
public widthUnit: string = '%';
public heightUnit: string = 'px';
public fullWidth: boolean = false;
public fullWidth: boolean = true;
public fullHeight: boolean = false;
public sizeOption: string = 'medium';
public sizeOption: string = 'full-width';
// Data properties
public dataSource: string = 'static';
......@@ -392,7 +392,7 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.customClickHandler = this.configObj.customClickHandler || '';
// Layout configuration
this.width = this.configObj.width || 600;
this.width = this.configObj.width || 100;
this.height = this.configObj.height || 400;
this.minWidth = this.configObj.minWidth || 400;
this.minHeight = this.configObj.minHeight || 300;
......@@ -400,9 +400,9 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
this.maxHeight = this.configObj.maxHeight || 800;
this.aspectRatio = this.configObj.aspectRatio || 'auto';
this.responsive = this.configObj.responsive !== undefined ? this.configObj.responsive : true;
this.widthUnit = this.configObj.widthUnit || 'px';
this.widthUnit = this.configObj.widthUnit || '%';
this.heightUnit = this.configObj.heightUnit || 'px';
this.fullWidth = this.configObj.fullWidth !== undefined ? this.configObj.fullWidth : false;
this.fullWidth = this.configObj.fullWidth !== undefined ? this.configObj.fullWidth : true;
this.fullHeight = this.configObj.fullHeight !== undefined ? this.configObj.fullHeight : false;
this.sizeOption = this.configObj.sizeOption || 'medium';
......@@ -843,8 +843,8 @@ export class SyncfusionDatagridWidgetComponent extends BaseWidgetComponent imple
'border-color': this.borderColor,
'border-radius': `${this.borderRadius}px`,
'border-width': `${this.borderWidth}px`,
'padding': `${this.padding}px`,
'margin': `${this.margin}px`,
'padding': '0px', // Force no padding for full width
'margin': '0px', // Force no margin for full width
'font-size': `${this.fontSize}px`,
'font-weight': this.fontWeight,
'font-family': this.fontFamily,
......
......@@ -469,11 +469,11 @@ export class SyncfusionPivotWidgetComponent extends BaseWidgetComponent implemen
onDataBound(args: any): void {
// Apply perspective after data is loaded and rendered, but only once.
if (this.perspective && !this.isPerspectiveApplied && this.perspective !== '{}') {
setTimeout(() => {
this.setWidgetState(this.perspective as string);
}, 50); // Small delay to ensure rendering is complete
}
// if (this.perspective && !this.isPerspectiveApplied && this.perspective !== '{}') {
// setTimeout(() => {
// this.setWidgetState(this.perspective as string);
// }, 50); // Small delay to ensure rendering is complete
// }
}
onReset(): void {
......
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