Commit 090de8bc by Ooh-Ao

หห

parent d608b7f9
# Gemini Project: myportal-Manage
This file provides context about the myportal-Manage project for the Gemini AI assistant.
## Project Overview
This is an Angular application named "ynex". It appears to be a comprehensive portal with features like authentication, data visualization (charts, grids), and various UI components. The project uses Bitbucket Pipelines for CI/CD and supports internationalization for English and Thai.
## Tech Stack
* **Framework:** Angular 17
* **UI Frameworks:**
* Tailwind CSS
* Angular Material
* Syncfusion (for data grids and pivot tables)
* ng-bootstrap
* **Styling:** SCSS
* **State Management:** Not explicitly defined, likely using RxJS with services.
* **Testing:** Karma and Jasmine
* **CI/CD:** Bitbucket Pipelines
* **Internationalization:** ngx-translate
## Project Structure
* `src/app`: Contains the main application logic.
* `src/app/components`: A large number of reusable UI components.
* `src/app/DPU`: Feature modules for the application.
* `src/app/authentication`: Authentication-related components.
* `src/app/shared`: Shared modules, services, and models.
* `src/assets`: Static assets like images, fonts, and styles.
* `src/assets/i18n`: Translation files.
* `angular.json`: Angular CLI configuration.
* `package.json`: Project dependencies and scripts.
* `bitbucket-pipelines.yml`: CI/CD configuration.
## Development
### Common Commands
* **Run the development server:**
```bash
npm start
```
* **Build the project:**
```bash
npm run build
```
* **Run tests:**
```bash
npm test
```
* **Compile SCSS to CSS:**
```bash
npm run postcss
```
### Internationalization (i18n)
The application uses `ngx-translate` for internationalization. Translation files are located in `src/assets/i18n`.
* `en.json`: English translations
* `th.json`: Thai translations
When adding new text that needs to be translated, you should add it to these files and use the `translate` pipe or service in the application.
...@@ -32,6 +32,9 @@ ...@@ -32,6 +32,9 @@
"@ks89/angular-modal-gallery": "^11.1.1", "@ks89/angular-modal-gallery": "^11.1.1",
"@ng-bootstrap/ng-bootstrap": "^16.0.0", "@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ng-select/ng-select": "^12.0.6", "@ng-select/ng-select": "^12.0.6",
"@ngrx/effects": "^17.0.0",
"@ngrx/store": "^17.0.0",
"@ngrx/store-devtools": "^17.0.0",
"@ngx-translate/core": "^15.0.0", "@ngx-translate/core": "^15.0.0",
"@ngx-translate/http-loader": "^8.0.0", "@ngx-translate/http-loader": "^8.0.0",
"@syncfusion/ej2-angular-base": "^29.2.4", "@syncfusion/ej2-angular-base": "^29.2.4",
......
...@@ -104,11 +104,6 @@ export const admin: Routes = [ ...@@ -104,11 +104,6 @@ export const admin: Routes = [
import('./myportal/view-list-course/view-list-course.component').then((m) => m.ViewListCourseComponent), import('./myportal/view-list-course/view-list-course.component').then((m) => m.ViewListCourseComponent),
}, },
{ {
path: 'view-list-widgets',
loadComponent: () =>
import('./myportal/view-list-widgets/view-list-widgets.component').then((m) => m.ViewListWidgetsComponent),
},
{
path: 'excel-report', path: 'excel-report',
loadComponent: () => loadComponent: () =>
import('./myportal/excel-report/excel-report.component').then((m) => m.ExcelReportComponent), import('./myportal/excel-report/excel-report.component').then((m) => m.ExcelReportComponent),
...@@ -134,11 +129,6 @@ export const admin: Routes = [ ...@@ -134,11 +129,6 @@ export const admin: Routes = [
import('./myportal/list-doc/list-doc.component').then((m) => m.ListDocComponent), import('./myportal/list-doc/list-doc.component').then((m) => m.ListDocComponent),
}, },
{ {
path: 'list-widgets',
loadComponent: () =>
import('./myportal/list-widgets/list-widgets.component').then((m) => m.ListWidgetsComponent),
},
{
path: 'excel-list', path: 'excel-list',
loadComponent: () => loadComponent: () =>
import('./myportal/set-excel-reports/excel-list/excel-list.component').then((m) => m.ExcelListComponent), import('./myportal/set-excel-reports/excel-list/excel-list.component').then((m) => m.ExcelListComponent),
......
::ng-deep ng2-dropdown-menu {
z-index: 9999 !important;
.ng2-dropdown-menu {
z-index: 9999 !important;
ng2-menu-item {
z-index: 9999 !important;
}
}
}
\ No newline at end of file
<app-page-header [title]="'รายการวิทเจ็ท'" [activeTitle]="'ผู้ดูแลระบบ'"
[title1]="'รายการวิทเจ็ท'"></app-page-header>
<!-- <div class="row">
<div class="col-12">
<div class="py-3">
<input type="text" class="form-control w-25" placeholder="ค้นหา" [(ngModel)]="search">
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-4 mb-3" *ngFor="let data of filterListWidget()">
<div class="card border-5 border border-widget h-100 shadow">
<div class=" p-2 border-5">
<img width="100" class="card-img-top cover" src="{{data.getImage()}}"
(click)="openEmployeeModal(data.getImage())">
</div>
<div class="card-body">
<h4 class="card-title">{{ data.widgetTname }}</h4>
<p class="card-text ">{{ data.thDesc }}</p>
<p class="text-info pointer mb-0" (click)="downloadFile(data.widgetId)"><i class="fas fa-download mr-1"></i>
ดาวน์โหลด <small class="text-muted" *ngIf="data.dwTime > 0">{{coverDate(data.downloadDate)}}
{{data.downloadTime}} ( {{data.dwTime}} ครั้ง)</small></p>
<p class="text-info pointer mb-0" (click)="openLink(data.link1)"><i class="fas fa-link mr-1"></i>
ตัวอย่างวิธีใช้งาน</p>
</div>
<div class="card-footer border-bottom-5">
<small class="text-muted">วันที่อัพโหลด {{coverDate(data.uploadDate)}} {{data.uploadTime}}</small>
</div>
</div>
</div>
</div> -->
<div class="box p-4">
<div class="flex flex-wrap -mx-2">
<div class="w-full">
<div class="py-3 px-2">
<input type="text"
class="block w-full md:w-1/4 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 shadow-sm"
placeholder="ค้นหา" [(ngModel)]="search" />
</div>
</div>
<div class="w-full sm:w-1/2 lg:w-1/3 xl:w-1/3 px-2 mb-4" *ngFor="let data of filterListWidget()">
<div
class="group bg-white rounded-lg overflow-hidden shadow-lg border-4 border-blue-600 h-full flex flex-col transform transition-all duration-300 ease-in-out hover:scale-[1.02] hover:shadow-xl">
<div class="p-4 flex justify-center items-center flex-shrink-0">
<img
class="w-full h-full object-cover rounded-md shadow-md transform transition-transform duration-300 group-hover:scale-105 cursor-pointer"
style="width: 400px; height: 200px; max-width: 100%;" src="{{ data.getImage() }}" alt="{{ data.widgetTname }}"
(click)="openDialog(data.getImage())" />
</div>
<div class="p-4 flex-grow">
<span class="text-xl font-semibold text-gray-800 mb-2" style="font-size: 18px;">
{{ data.widgetTname }}
</span>
<p class="text-gray-700 text-sm mb-3">{{ data.thDesc }}</p>
<div class="mb-0 flex items-center justify-center sm:justify-start gap-2 w-1/2 mt-5">
<i class="fa fa-link text-blue-600 text-base flex-shrink-0" aria-hidden="true"></i>
<input type="text" [value]="'ตัวอย่างวิธีใช้งาน'" (click)="openLink(data.link1)"
style="background-color: rgb(76, 117, 207, 1); color: #FFF;"
class="flex-grow border border-gray-300 rounded-md px-2 py-1 text-blue-600 cursor-pointer hover:underline focus:outline-none focus:ring-1 focus:ring-blue-500 transition-all duration-200"
readonly />
</div>
<div class="mb-2 flex items-center justify-center sm:justify-start gap-2 w-1/2">
<i class="fa fa-download text-blue-600 text-base flex-shrink-0" aria-hidden="true"></i>
<input type="text" [value]="'ดาวน์โหลด'" (click)="downloadFile(data.widgetId)"
style="background-color: rgb(34, 197, 94, 1); color: #FFF;"
class="flex-grow border border-gray-300 rounded-md px-2 py-1 text-blue-600 cursor-pointer hover:underline focus:outline-none focus:ring-1 focus:ring-blue-500 transition-all duration-200"
readonly />
</div>
</div>
<div class="px-4 py-3 border-t border-gray-200 text-right flex-shrink-0">
<small class="text-gray-500 text-xs">วันที่อัพโหลด {{ coverDate(data.uploadDate) }}
{{ data.uploadTime }}</small>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
import { AlertModalComponent } from '../alert-modal/alert-modal.component';
import { OpenImageComponent } from '../open-image/open-image.component';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { SharedModule } from '../../../../shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { NgSelectModule } from '@ng-select/ng-select';
import saveAs from 'file-saver';
import { WidgetModel } from '../../../models/widgets.model';
import { WidgetService } from '../../../services/widgets.service';
@Component({
selector: 'app-view-list-widgets',
templateUrl: './view-list-widgets.component.html',
styleUrls: ['./view-list-widgets.component.scss'],
standalone: true,
imports: [
CommonModule,
FormsModule,
NgSelectModule,
SharedModule,
MatDialogModule,
NgbPaginationModule,
TranslateModule,
],
})
export class ViewListWidgetsComponent implements OnInit {
page = 1;
pageSize = 10;
listWidget:WidgetModel[]=[]
search:string = ''
dialogRef: any;
constructor(private modalService: NgbModal,private widgetService:WidgetService, private dialog: MatDialog,) {
}
openDialog(image: string) {
const dialogConfig = {
width: '750px',
disableClose: false,
data: {
linkImage: image
},
panelClass: 'my-dialog-img-preview',
};
this.dialogRef = this.dialog.open(OpenImageComponent, dialogConfig);
this.dialogRef.afterClosed().subscribe((result: any) => {
console.log('The dialog was closed', result);
}, (reason: any) => {
});
}
async downloadFile(logId: string) {
try {
const data = await this.widgetService.downloadFile(logId).toPromise();
if (data) {
saveAs(new Blob([data]), "file_download.xlsx");
}
} catch (error) {
console.error('Error loading data:', error);
}
}
// getStatus(status: string) {
// if (status == '0') {
// return 'Private'
// } else if (status == '1') {
// return 'Public'
// }
// }
filterListWidget(){
return this.listWidget.filter(x => x.widgetTname.toLowerCase().includes(this.search.toLowerCase())||x.widgetEname.toLowerCase().includes(this.search.toLowerCase()))
}
async getListExcel(){
try {
const data = await this.widgetService.getListWidgets().toPromise();
this.listWidget = data!.map(x => new WidgetModel(x))
} catch (error) {
console.error('Error loading data:', error);
}
}
ngOnInit() {
this.getListExcel();
}
openLink(url:string){
window.open(url, "_blank");
}
openAlertModal(message?: string) {
const modalRef = this.modalService.open(AlertModalComponent, {
centered: true,
backdrop: 'static'
})
modalRef.componentInstance.message = message ? message : ""
modalRef.result.then(result => {
this.modalService.dismissAll()
}, reason => {
this.modalService.dismissAll()
})
}
coverDate(date:string){
return date.split('-').reverse().join('/')
}
}
<div *ngIf="errorMessage$ | async as errorMessage" class="alert alert-danger">{{errorMessage}}</div>
<div class="flex h-screen bg-gray-50"> <div class="flex h-screen bg-gray-50">
<!-- Widget Sidebar --> <!-- Widget Sidebar -->
<div class="w-72 bg-white p-4 overflow-y-auto shadow-lg border-r flex flex-col"> <div class="w-72 bg-white p-4 overflow-y-auto shadow-lg border-r flex flex-col">
...@@ -40,10 +42,11 @@ ...@@ -40,10 +42,11 @@
class="p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white"> class="p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white">
<option *ngFor="let dash of userDashboards" [value]="dash.id">{{ dash.name }}</option> <option *ngFor="let dash of userDashboards" [value]="dash.id">{{ dash.name }}</option>
</select> </select>
<div class="relative flex items-center"> <app-dataset-picker (datasetSelected)="onDatasetSelected($event)"></app-dataset-picker>
<div *ngIf="dashboardData" class="relative flex items-center">
<input type="text" [(ngModel)]="dashboardData.name" (blur)="saveDashboardName()" <input type="text" [(ngModel)]="dashboardData.name" (blur)="saveDashboardName()"
class="p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white text-gray-800 font-semibold"> class="p-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500 bg-white text-gray-800 font-semibold">
<button *ngIf="dashboardData?.name !== dashboardData?.originalName" (click)="saveDashboardName()" <button (click)="saveDashboardName()"
class="ml-2 text-emerald-500 hover:text-emerald-700 focus:outline-none"> class="ml-2 text-emerald-500 hover:text-emerald-700 focus:outline-none">
<i class="bi bi-check-circle-fill text-2xl"></i> <i class="bi bi-check-circle-fill text-2xl"></i>
</button> </button>
......
<div class="dataset-picker-container">
<label for="dataset-select">Select Dataset:</label>
<select id="dataset-select" (change)="onDatasetChange($event)">
<option value="">-- Please choose a dataset --</option>
<option *ngFor="let dataset of datasets$ | async" [value]="dataset.id">
{{ dataset.name }}
</option>
</select>
</div>
.dataset-picker-container {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
label {
font-weight: bold;
}
select {
padding: 8px;
border-radius: 4px;
border: 1px solid #ccc;
}
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Observable } from 'rxjs';
import { DatasetModel } from '../models/widgets.model';
import { DatasetService } from '../services/dataset.service';
@Component({
selector: 'app-dataset-picker',
standalone: true,
imports: [CommonModule, FormsModule],
templateUrl: './dataset-picker.component.html',
styleUrls: ['./dataset-picker.component.scss']
})
export class DatasetPickerComponent implements OnInit {
datasets$: Observable<DatasetModel[]>;
@Output() datasetSelected = new EventEmitter<string>();
constructor(private datasetService: DatasetService) { }
ngOnInit(): void {
this.datasets$ = this.datasetService.getDatasets();
}
onDatasetChange(event: any): void {
this.datasetSelected.emit(event.target.value);
}
}
<div class="container mx-auto p-4 bg-gray-50 min-h-screen"> <div *ngIf="errorMessage" class="alert alert-danger">{{errorMessage}}</div>
<div class="container mx-auto p-4">
<h2 class="text-2xl font-bold mb-4 text-gray-800">Viewing Dashboard: {{ dashboardName }}</h2> <h2 class="text-2xl font-bold mb-4 text-gray-800">Viewing Dashboard: {{ dashboardName }}</h2>
<div class="control-section"> <div class="control-section">
<ejs-dashboardlayout id='dashboard_viewer' #viewerLayout [cellSpacing]="cellSpacing" [panels]="panels" [columns]="6" [allowResizing]="false" [allowDragging]="false"> <ejs-dashboardlayout id='dashboard_viewer' #viewerLayout [cellSpacing]="cellSpacing" [panels]="panels" [columns]="6" [allowResizing]="false" [allowDragging]="false">
......
import { Component, OnInit, Type } from '@angular/core'; // Import Type import { Component, OnInit, Type, ChangeDetectionStrategy } from '@angular/core'; // Import Type
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ActivatedRoute, RouterModule } from '@angular/router'; import { ActivatedRoute, RouterModule } from '@angular/router';
import { DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts'; import { DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts';
import { DashboardService } from '../../shared/services/dashboard.service'; import { Store } from '@ngrx/store';
import { DashboardLayout, Widget } from '../../shared/models/dashboard.model';
import { map, switchMap } from 'rxjs/operators'; import { map, switchMap } from 'rxjs/operators';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { NgComponentOutlet } from '@angular/common'; // Import NgComponentOutlet import { NgComponentOutlet } from '@angular/common'; // Import NgComponentOutlet
import * as DashboardActions from '../state/dashboard.actions';
import * as DashboardSelectors from '../state/dashboard.selectors';
import { DashboardModel } from '../models/widgets.model';
// Import all the widget components // Import all the widget components
import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component'; import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
...@@ -29,7 +31,8 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg ...@@ -29,7 +31,8 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg
standalone: true, standalone: true,
imports: [CommonModule, RouterModule, DashboardLayoutModule, NgComponentOutlet, CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, KpiWidgetComponent, WelcomeWidgetComponent, ChartWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent], imports: [CommonModule, RouterModule, DashboardLayoutModule, NgComponentOutlet, CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, KpiWidgetComponent, WelcomeWidgetComponent, ChartWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent],
templateUrl: './dashboard-viewer.component.html', templateUrl: './dashboard-viewer.component.html',
styleUrls: ['./dashboard-viewer.component.scss'] styleUrls: ['./dashboard-viewer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class DashboardViewerComponent implements OnInit { export class DashboardViewerComponent implements OnInit {
...@@ -54,9 +57,11 @@ export class DashboardViewerComponent implements OnInit { ...@@ -54,9 +57,11 @@ export class DashboardViewerComponent implements OnInit {
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent 'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent
}; };
public errorMessage: string | null = null;
constructor( constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private dashboardService: DashboardService private store: Store
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
...@@ -67,19 +72,22 @@ export class DashboardViewerComponent implements OnInit { ...@@ -67,19 +72,22 @@ export class DashboardViewerComponent implements OnInit {
console.error('Dashboard ID is missing from the route.'); console.error('Dashboard ID is missing from the route.');
return of(null); return of(null);
} }
return this.dashboardService.getDashboardLayout(id); this.store.dispatch(DashboardActions.loadDashboards());
return this.store.select(DashboardSelectors.selectDashboardById(id));
}) })
).subscribe(dashboard => { ).subscribe({
if (dashboard) { next: dashboard => {
this.loadDashboard(dashboard); if (dashboard) {
} this.loadDashboard(dashboard);
}
},
error: err => this.errorMessage = err.message
}); });
} }
loadDashboard(dashboard: DashboardLayout): void { loadDashboard(dashboard: DashboardModel): void {
this.dashboardName = dashboard.name; this.dashboardName = dashboard.name;
// Map widgets from the layout to Syncfusion PanelModels, attaching componentType this.panels = dashboard.widgets.map(widget => ({
this.panels = dashboard.widgets.map((widget: Widget) => ({
id: widget.id, id: widget.id,
row: widget.y, row: widget.y,
col: widget.x, col: widget.x,
...@@ -87,7 +95,7 @@ export class DashboardViewerComponent implements OnInit { ...@@ -87,7 +95,7 @@ export class DashboardViewerComponent implements OnInit {
sizeY: widget.rows, sizeY: widget.rows,
header: widget.name, header: widget.name,
componentType: this.widgetComponentMap[widget.component], // Attach the component Type componentType: this.widgetComponentMap[widget.component], // Attach the component Type
componentInputs: widget.inputs // Pass inputs if available componentInputs: widget.data // Pass inputs if available
})); }));
} }
} }
...@@ -2,88 +2,77 @@ import { environment } from "../../../environments/environment"; ...@@ -2,88 +2,77 @@ import { environment } from "../../../environments/environment";
import { TagModel } from "./tag.mmodel" import { TagModel } from "./tag.mmodel"
import { GroupModel } from "./group.mmodel" import { GroupModel } from "./group.mmodel"
export interface WidgetModel { export interface IDataset {
widgetId: string id: string;
widgetTname: string name: string;
widgetEname: string url: string;
thDesc: string
engDesc: string
chartType: string
widgetHeight: number
widgetWidth: number
seriesColumn: string
picture: string
remark: string
status: number
link1: string
dbSupport: string
widgetObj: string
fileType: string
isPivot: number
isDataGrid: number
uploadBy: string
uploadDate: string
uploadTime: string
group: GroupModel
tags: TagModel[]
} }
export class WidgetModel implements WidgetModel { export class DatasetModel implements IDataset {
widgetId: string id: string;
widgetTname: string name: string;
widgetEname: string url: string;
thDesc: string
engDesc: string
chartType: string
widgetHeight: number
widgetWidth: number
seriesColumn: string
picture: string
remark: string
status: number
link1: string
dbSupport: string
widgetObj: string
fileType: string
isPivot: number
isDataGrid: number
uploadBy: string
uploadDate: string
uploadTime: string
group: GroupModel
tags: TagModel[]
constructor(data: Partial<WidgetModel>) { constructor(data: Partial<DatasetModel>) {
this.widgetId = data.widgetId ?? '' this.id = data.id ?? '';
this.widgetTname = data.widgetTname ?? '' this.name = data.name ?? '';
this.widgetEname = data.widgetEname ?? '' this.url = data.url ?? '';
this.thDesc = data.thDesc ?? '' }
this.engDesc = data.engDesc ?? '' }
this.chartType = data.chartType ?? ''
this.widgetHeight = data.widgetHeight ?? 0 export interface IWidget {
this.widgetWidth = data.widgetWidth ?? 0 id: string;
this.status = data.status ?? 0 name: string;
this.seriesColumn = data.seriesColumn ?? '' component: string;
this.picture = data.picture ?? '' cols: number;
this.remark = data.remark ?? '' rows: number;
this.link1 = data.link1 ?? '' x: number;
this.widgetObj = data.widgetObj ?? '' y: number;
this.dbSupport = data.dbSupport ?? '' data: any;
this.tags = data.tags ?? [] }
this.fileType = data.fileType ?? ''
this.isPivot = data.isPivot ?? 0 export class WidgetModel implements IWidget {
this.isDataGrid = data.isDataGrid ?? 0 id: string;
this.uploadBy = data.uploadBy ?? '' name: string;
this.uploadDate = data.uploadDate ?? '' component: string;
this.uploadTime = data.uploadTime ?? '' cols: number;
this.group = data.group ? new GroupModel(data.group) : new GroupModel({}) rows: number;
} x: number;
y: number;
data: any;
constructor(data: Partial<IWidget>) {
this.id = data.id ?? '';
this.name = data.name ?? '';
this.component = data.component ?? '';
this.cols = data.cols ?? 1;
this.rows = data.rows ?? 1;
this.x = data.x ?? 0;
this.y = data.y ?? 0;
this.data = data.data ?? {};
}
}
export interface IDashboard {
id: string;
name: string;
description: string;
datasetId?: string;
widgets: IWidget[];
}
getImage(): string { export class DashboardModel implements IDashboard {
if (this.picture) { id: string;
return environment.url + "files/image/" + this.picture name: string;
} else { description: string;
return 'assets/images/big/auth-bg.jpg' datasetId?: string;
} widgets: IWidget[];
}
} constructor(data: Partial<IDashboard>) {
\ No newline at end of file this.id = data.id ?? '';
this.name = data.name ?? '';
this.description = data.description ?? '';
this.datasetId = data.datasetId;
this.widgets = data.widgets ? data.widgets.map(w => new WidgetModel(w)) : [];
}
}
::ng-deep ng2-dropdown-menu {
z-index: 9999 !important;
.ng2-dropdown-menu {
z-index: 9999 !important;
ng2-menu-item {
z-index: 9999 !important;
}
}
}
\ No newline at end of file
<app-page-header [title]="'รายการวิทเจ็ท'" [activeTitle]="'ผู้ดูแลระบบ'"
[title1]="'รายการวิทเจ็ท'"></app-page-header>
<!-- <div class="row">
<div class="col-12">
<div class="py-3">
<input type="text" class="form-control w-25" placeholder="ค้นหา" [(ngModel)]="search">
</div>
</div>
<div class="col-sm-6 col-md-4 col-lg-4 mb-3" *ngFor="let data of filterListWidget()">
<div class="card border-5 border border-widget h-100 shadow">
<div class=" p-2 border-5">
<img width="100" class="card-img-top cover" src="{{data.getImage()}}"
(click)="openEmployeeModal(data.getImage())">
</div>
<div class="card-body">
<h4 class="card-title">{{ data.widgetTname }}</h4>
<p class="card-text ">{{ data.thDesc }}</p>
<p class="text-info pointer mb-0" (click)="downloadFile(data.widgetId)"><i class="fas fa-download mr-1"></i>
ดาวน์โหลด <small class="text-muted" *ngIf="data.dwTime > 0">{{coverDate(data.downloadDate)}}
{{data.downloadTime}} ( {{data.dwTime}} ครั้ง)</small></p>
<p class="text-info pointer mb-0" (click)="openLink(data.link1)"><i class="fas fa-link mr-1"></i>
ตัวอย่างวิธีใช้งาน</p>
</div>
<div class="card-footer border-bottom-5">
<small class="text-muted">วันที่อัพโหลด {{coverDate(data.uploadDate)}} {{data.uploadTime}}</small>
</div>
</div>
</div>
</div> -->
<div class="box p-4">
<div class="flex flex-wrap -mx-2">
<div class="w-full">
<div class="py-3 px-2">
<input type="text"
class="block w-full md:w-1/4 px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 shadow-sm"
placeholder="ค้นหา" [(ngModel)]="search" />
</div>
</div>
<div class="w-full sm:w-1/2 lg:w-1/3 xl:w-1/3 px-2 mb-4" *ngFor="let data of filterListWidget()">
<div
class="group bg-white rounded-lg overflow-hidden shadow-lg border-4 border-blue-600 h-full flex flex-col transform transition-all duration-300 ease-in-out hover:scale-[1.02] hover:shadow-xl">
<div class="p-4 flex justify-center items-center flex-shrink-0">
<img
class="w-full h-full object-cover rounded-md shadow-md transform transition-transform duration-300 group-hover:scale-105 cursor-pointer"
style="width: 400px; height: 200px; max-width: 100%;" src="{{ data.getImage() }}" alt="{{ data.widgetTname }}"
(click)="openDialog(data.getImage())" />
</div>
<div class="p-4 flex-grow">
<span class="text-xl font-semibold text-gray-800 mb-2" style="font-size: 18px;">
{{ data.widgetTname }}
</span>
<p class="text-gray-700 text-sm mb-3">{{ data.thDesc }}</p>
<div class="mb-0 flex items-center justify-center sm:justify-start gap-2 w-1/2 mt-5">
<i class="fa fa-link text-blue-600 text-base flex-shrink-0" aria-hidden="true"></i>
<input type="text" [value]="'ตัวอย่างวิธีใช้งาน'" (click)="openLink(data.link1)"
style="background-color: rgb(76, 117, 207, 1); color: #FFF;"
class="flex-grow border border-gray-300 rounded-md px-2 py-1 text-blue-600 cursor-pointer hover:underline focus:outline-none focus:ring-1 focus:ring-blue-500 transition-all duration-200"
readonly />
</div>
<div class="mb-2 flex items-center justify-center sm:justify-start gap-2 w-1/2">
<i class="fa fa-download text-blue-600 text-base flex-shrink-0" aria-hidden="true"></i>
<input type="text" [value]="'ดาวน์โหลด'" (click)="downloadFile(data.widgetId)"
style="background-color: rgb(34, 197, 94, 1); color: #FFF;"
class="flex-grow border border-gray-300 rounded-md px-2 py-1 text-blue-600 cursor-pointer hover:underline focus:outline-none focus:ring-1 focus:ring-blue-500 transition-all duration-200"
readonly />
</div>
</div>
<div class="px-4 py-3 border-t border-gray-200 text-right flex-shrink-0">
<small class="text-gray-500 text-xs">วันที่อัพโหลด {{ coverDate(data.uploadDate) }}
{{ data.uploadTime }}</small>
</div>
</div>
</div>
</div>
</div>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
import { AlertModalComponent } from '../alert-modal/alert-modal.component';
import { OpenImageComponent } from '../open-image/open-image.component';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
import { SharedModule } from '../../../../shared/shared.module';
import { TranslateModule } from '@ngx-translate/core';
import { NgSelectModule } from '@ng-select/ng-select';
import saveAs from 'file-saver';
import { WidgetModel } from '../../../models/widgets.model';
import { WidgetService } from '../../../services/widgets.service';
@Component({
selector: 'app-view-list-widgets',
templateUrl: './view-list-widgets.component.html',
styleUrls: ['./view-list-widgets.component.scss'],
standalone: true,
imports: [
CommonModule,
FormsModule,
NgSelectModule,
SharedModule,
MatDialogModule,
NgbPaginationModule,
TranslateModule,
],
})
export class ViewListWidgetsComponent implements OnInit {
page = 1;
pageSize = 10;
listWidget:WidgetModel[]=[]
search:string = ''
dialogRef: any;
constructor(private modalService: NgbModal,private widgetService:WidgetService, private dialog: MatDialog,) {
}
openDialog(image: string) {
const dialogConfig = {
width: '750px',
disableClose: false,
data: {
linkImage: image
},
panelClass: 'my-dialog-img-preview',
};
this.dialogRef = this.dialog.open(OpenImageComponent, dialogConfig);
this.dialogRef.afterClosed().subscribe((result: any) => {
console.log('The dialog was closed', result);
}, (reason: any) => {
});
}
async downloadFile(logId: string) {
try {
const data = await this.widgetService.downloadFile(logId).toPromise();
if (data) {
saveAs(new Blob([data]), "file_download.xlsx");
}
} catch (error) {
console.error('Error loading data:', error);
}
}
// getStatus(status: string) {
// if (status == '0') {
// return 'Private'
// } else if (status == '1') {
// return 'Public'
// }
// }
filterListWidget(){
return this.listWidget.filter(x => x.widgetTname.toLowerCase().includes(this.search.toLowerCase())||x.widgetEname.toLowerCase().includes(this.search.toLowerCase()))
}
async getListExcel(){
try {
const data = await this.widgetService.getListWidgets().toPromise();
this.listWidget = data!.map(x => new WidgetModel(x))
} catch (error) {
console.error('Error loading data:', error);
}
}
ngOnInit() {
this.getListExcel();
}
openLink(url:string){
window.open(url, "_blank");
}
openAlertModal(message?: string) {
const modalRef = this.modalService.open(AlertModalComponent, {
centered: true,
backdrop: 'static'
})
modalRef.componentInstance.message = message ? message : ""
modalRef.result.then(result => {
this.modalService.dismissAll()
}, reason => {
this.modalService.dismissAll()
})
}
coverDate(date:string){
return date.split('-').reverse().join('/')
}
}
...@@ -30,11 +30,6 @@ export const myportal: Routes = [ ...@@ -30,11 +30,6 @@ export const myportal: Routes = [
import('../myskill-x/myportal/view-list-course/view-list-course.component').then((m) => m.ViewListCourseComponent), import('../myskill-x/myportal/view-list-course/view-list-course.component').then((m) => m.ViewListCourseComponent),
}, },
{ {
path: 'view-list-widgets',
loadComponent: () =>
import('../myskill-x/myportal/view-list-widgets/view-list-widgets.component').then((m) => m.ViewListWidgetsComponent),
},
{
path: 'excel-report', path: 'excel-report',
loadComponent: () => loadComponent: () =>
import('../myskill-x/myportal/excel-report/excel-report.component').then((m) => m.ExcelReportComponent), import('../myskill-x/myportal/excel-report/excel-report.component').then((m) => m.ExcelReportComponent),
...@@ -60,11 +55,6 @@ export const myportal: Routes = [ ...@@ -60,11 +55,6 @@ export const myportal: Routes = [
import('../myskill-x/myportal/list-doc/list-doc.component').then((m) => m.ListDocComponent), import('../myskill-x/myportal/list-doc/list-doc.component').then((m) => m.ListDocComponent),
}, },
{ {
path: 'list-widgets',
loadComponent: () =>
import('../myskill-x/myportal/list-widgets/list-widgets.component').then((m) => m.ListWidgetsComponent),
},
{
path: 'excel-list', path: 'excel-list',
loadComponent: () => loadComponent: () =>
import('../myskill-x/myportal/set-excel-reports/excel-list/excel-list.component').then((m) => m.ExcelListComponent), import('../myskill-x/myportal/set-excel-reports/excel-list/excel-list.component').then((m) => m.ExcelListComponent),
...@@ -154,4 +144,4 @@ export const myportal: Routes = [ ...@@ -154,4 +144,4 @@ export const myportal: Routes = [
}) })
export class MyskillXModule { export class MyskillXModule {
static routes = myportal; static routes = myportal;
} }
\ No newline at end of file
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { DatasetModel } from '../models/widgets.model';
@Injectable({
providedIn: 'root'
})
export class DatasetService {
private datasets: DatasetModel[] = [
new DatasetModel({ id: '1', name: 'Sample Dataset 1', url: 'assets/data/sample1.json' }),
new DatasetModel({ id: '2', name: 'Sample Dataset 2', url: 'assets/data/sample2.json' })
];
constructor() { }
getDatasets(): Observable<DatasetModel[]> {
return of(this.datasets);
}
getDatasetById(id: string): Observable<DatasetModel | undefined> {
return of(this.datasets.find(d => d.id === id));
}
}
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { DashboardModel, WidgetModel } from '../models/widgets.model';
export const MOCK_DASHBOARD_DATA: DashboardModel[] = [
{
id: '1',
name: 'Sales Dashboard',
description: 'Dashboard showing sales performance over time.',
widgets: [
{ id: '101', name: 'Monthly Sales', component: 'ChartWidgetComponent', cols: 2, rows: 1, x: 0, y: 0, data: { type: 'bar', labels: ['Jan', 'Feb', 'Mar'], datasets: [{ data: [65, 59, 80], label: 'Series A' }] } },
{ id: '102', name: 'Top Products', component: 'SyncfusionDatagridWidgetComponent', cols: 2, rows: 1, x: 2, y: 0, data: { columns: ['Product', 'Sales'], data: [{ Product: 'A', Sales: 100 }, { Product: 'B', Sales: 150 }] } }
]
},
{
id: '2',
name: 'Marketing Overview',
description: 'Overview of marketing campaign performance.',
widgets: [
{ id: '201', name: 'Website Traffic', component: 'ChartWidgetComponent', cols: 2, rows: 1, x: 0, y: 0, data: { type: 'line', labels: ['Week 1', 'Week 2', 'Week 3'], datasets: [{ data: [120, 150, 130], label: 'Visitors' }] } },
{ id: '202', name: 'Conversion Rate', component: 'KpiWidgetComponent', cols: 1, rows: 1, x: 2, y: 0, data: { value: '2.5%', label: 'Conversion' } }
]
}
];
@Injectable({
providedIn: 'root'
})
export class MockDashboardService {
constructor() { }
getDashboards(): Observable<DashboardModel[]> {
return of(MOCK_DASHBOARD_DATA);
}
getDashboardById(id: string): Observable<DashboardModel | undefined> {
const dashboard = MOCK_DASHBOARD_DATA.find(d => d.id === id);
return of(dashboard);
}
}
...@@ -36,7 +36,7 @@ export class WidgetService { ...@@ -36,7 +36,7 @@ export class WidgetService {
} }
deleteWidget(model: WidgetModel): Observable<any> { deleteWidget(model: WidgetModel): Observable<any> {
let body = { let body = {
widgetId: model.widgetId widgetId: model.id
} }
let option = { let option = {
headers: new HttpHeaders({ headers: new HttpHeaders({
......
import { createAction, props } from '@ngrx/store';
import { DashboardModel, WidgetModel, DatasetModel } from '../models/widgets.model';
export const loadDashboards = createAction('[Dashboard] Load Dashboards');
export const loadDashboardsSuccess = createAction('[Dashboard] Load Dashboards Success', props<{ dashboards: DashboardModel[] }>());
export const loadDashboardsFailure = createAction('[Dashboard] Load Dashboards Failure', props<{ error: any }>());
export const loadWidgets = createAction('[Dashboard] Load Widgets');
export const loadWidgetsSuccess = createAction('[Dashboard] Load Widgets Success', props<{ widgets: WidgetModel[] }>());
export const loadWidgetsFailure = createAction('[Dashboard] Load Widgets Failure', props<{ error: any }>());
export const loadDatasets = createAction('[Dashboard] Load Datasets');
export const loadDatasetsSuccess = createAction('[Dashboard] Load Datasets Success', props<{ datasets: DatasetModel[] }>());
export const loadDatasetsFailure = createAction('[Dashboard] Load Datasets Failure', props<{ error: any }>());
export const addDashboard = createAction('[Dashboard] Add Dashboard', props<{ dashboard: DashboardModel }>());
export const addDashboardSuccess = createAction('[Dashboard] Add Dashboard Success', props<{ dashboard: DashboardModel }>());
export const addDashboardFailure = createAction('[Dashboard] Add Dashboard Failure', props<{ error: any }>());
export const updateDashboard = createAction('[Dashboard] Update Dashboard', props<{ dashboard: DashboardModel }>());
export const updateDashboardSuccess = createAction('[Dashboard] Update Dashboard Success', props<{ dashboard: DashboardModel }>());
export const updateDashboardFailure = createAction('[Dashboard] Update Dashboard Failure', props<{ error: any }>());
export const deleteDashboard = createAction('[Dashboard] Delete Dashboard', props<{ id: string }>());
export const deleteDashboardSuccess = createAction('[Dashboard] Delete Dashboard Success', props<{ id: string }>());
export const deleteDashboardFailure = createAction('[Dashboard] Delete Dashboard Failure', props<{ error: any }>());
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { DashboardDataService } from '../../shared/services/dashboard-data.service';
import * as DashboardActions from './dashboard.actions';
@Injectable()
export class DashboardEffects {
loadDashboards$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.loadDashboards),
mergeMap(() => this.dashboardDataService.getDashboards()
.pipe(
map(dashboards => DashboardActions.loadDashboardsSuccess({ dashboards })),
catchError(error => of(DashboardActions.loadDashboardsFailure({ error })))
))
));
loadWidgets$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.loadWidgets),
mergeMap(() => this.dashboardDataService.getWidgets()
.pipe(
map(widgets => DashboardActions.loadWidgetsSuccess({ widgets })),
catchError(error => of(DashboardActions.loadWidgetsFailure({ error })))
))
));
loadDatasets$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.loadDatasets),
mergeMap(() => this.dashboardDataService.getDatasets()
.pipe(
map(datasets => DashboardActions.loadDatasetsSuccess({ datasets })),
catchError(error => of(DashboardActions.loadDatasetsFailure({ error })))
))
));
addDashboard$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.addDashboard),
mergeMap(action => this.dashboardDataService.addDashboard(action.dashboard)
.pipe(
map(dashboard => DashboardActions.addDashboardSuccess({ dashboard })),
catchError(error => of(DashboardActions.addDashboardFailure({ error })))
))
));
updateDashboard$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.updateDashboard),
mergeMap(action => this.dashboardDataService.updateDashboard(action.dashboard)
.pipe(
map(dashboard => DashboardActions.updateDashboardSuccess({ dashboard })),
catchError(error => of(DashboardActions.updateDashboardFailure({ error })))
))
));
deleteDashboard$ = createEffect(() => this.actions$.pipe(
ofType(DashboardActions.deleteDashboard),
mergeMap(action => this.dashboardDataService.deleteDashboard(action.id)
.pipe(
map(() => DashboardActions.deleteDashboardSuccess({ id: action.id })),
catchError(error => of(DashboardActions.deleteDashboardFailure({ error })))
))
));
constructor(
private actions$: Actions,
private dashboardDataService: DashboardDataService
) {}
}
import { createReducer, on } from '@ngrx/store';
import { DashboardModel, WidgetModel, DatasetModel } from '../models/widgets.model';
import * as DashboardActions from './dashboard.actions';
export interface DashboardState {
dashboards: DashboardModel[];
widgets: WidgetModel[];
datasets: DatasetModel[];
error: any;
}
export const initialState: DashboardState = {
dashboards: [],
widgets: [],
datasets: [],
error: null,
};
export const dashboardReducer = createReducer(
initialState,
on(DashboardActions.loadDashboardsSuccess, (state, { dashboards }) => ({ ...state, dashboards })),
on(DashboardActions.loadDashboardsFailure, (state, { error }) => ({ ...state, error })),
on(DashboardActions.loadWidgetsSuccess, (state, { widgets }) => ({ ...state, widgets })),
on(DashboardActions.loadWidgetsFailure, (state, { error }) => ({ ...state, error })),
on(DashboardActions.loadDatasetsSuccess, (state, { datasets }) => ({ ...state, datasets })),
on(DashboardActions.loadDatasetsFailure, (state, { error }) => ({ ...state, error })),
on(DashboardActions.addDashboardSuccess, (state, { dashboard }) => ({ ...state, dashboards: [...state.dashboards, dashboard] })),
on(DashboardActions.addDashboardFailure, (state, { error }) => ({ ...state, error })),
on(DashboardActions.updateDashboardSuccess, (state, { dashboard }) => ({
...state,
dashboards: state.dashboards.map(d => d.id === dashboard.id ? dashboard : d)
})),
on(DashboardActions.updateDashboardFailure, (state, { error }) => ({ ...state, error })),
on(DashboardActions.deleteDashboardSuccess, (state, { id }) => ({
...state,
dashboards: state.dashboards.filter(d => d.id !== id)
})),
on(DashboardActions.deleteDashboardFailure, (state, { error }) => ({ ...state, error }))
);
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { DashboardState } from './dashboard.reducer';
import { DashboardModel } from '../models/widgets.model';
export const selectDashboardState = createFeatureSelector<DashboardState>('dashboard');
export const selectAllDashboards = createSelector(
selectDashboardState,
(state: DashboardState) => state.dashboards
);
export const selectAllWidgets = createSelector(
selectDashboardState,
(state: DashboardState) => state.widgets
);
export const selectAllDatasets = createSelector(
selectDashboardState,
(state: DashboardState) => state.datasets
);
export const selectDashboardById = (id: string) => createSelector(
selectAllDashboards,
(dashboards: DashboardModel[]) => dashboards.find(d => d.id === id)
);
export const selectError = createSelector(
selectDashboardState,
(state: DashboardState) => state.error
);
...@@ -2,10 +2,11 @@ ...@@ -2,10 +2,11 @@
import { Component, OnInit, Type } from '@angular/core'; import { Component, OnInit, Type } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { DashboardService } from '../../shared/services/dashboard.service';
import { Widget } from '../../shared/models/dashboard.model';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgComponentOutlet } from '@angular/common'; import { NgComponentOutlet } from '@angular/common';
import { Store } from '@ngrx/store';
import * as DashboardActions from '../state/dashboard.actions';
import { WidgetModel } from '../models/widgets.model';
// Import all the widget components // Import all the widget components
import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component'; import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
...@@ -53,13 +54,12 @@ export class WidgetFormComponent implements OnInit { ...@@ -53,13 +54,12 @@ export class WidgetFormComponent implements OnInit {
constructor( constructor(
private fb: FormBuilder, private fb: FormBuilder,
private dashboardService: DashboardService, private store: Store,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.appName = this.route.snapshot.paramMap.get('appName') || '';
this.widgetId = this.route.snapshot.paramMap.get('widgetId'); this.widgetId = this.route.snapshot.paramMap.get('widgetId');
this.widgetForm = this.fb.group({ this.widgetForm = this.fb.group({
...@@ -69,17 +69,19 @@ export class WidgetFormComponent implements OnInit { ...@@ -69,17 +69,19 @@ export class WidgetFormComponent implements OnInit {
cols: [1, [Validators.required, Validators.min(1)]], cols: [1, [Validators.required, Validators.min(1)]],
rows: [1, [Validators.required, Validators.min(1)]], rows: [1, [Validators.required, Validators.min(1)]],
x: [0], x: [0],
y: [0] y: [0],
data: [null]
}); });
if (this.widgetId && this.widgetId !== 'new') { if (this.widgetId && this.widgetId !== 'new') {
this.isNew = false; this.isNew = false;
this.dashboardService.getWidget(this.widgetId).subscribe(widget => { this.store.dispatch(DashboardActions.loadWidgets());
if (widget) { // this.store.select(DashboardSelectors.selectWidgetById(this.widgetId)).subscribe(widget => {
this.widgetForm.patchValue(widget); // if (widget) {
this.updatePreview(widget.component); // this.widgetForm.patchValue(widget);
} // this.updatePreview(widget.component);
}); // }
// });
} }
// Subscribe to component name changes for live preview // Subscribe to component name changes for live preview
...@@ -97,10 +99,13 @@ export class WidgetFormComponent implements OnInit { ...@@ -97,10 +99,13 @@ export class WidgetFormComponent implements OnInit {
return; return;
} }
const widgetData: Widget = this.widgetForm.value; const widgetData: WidgetModel = this.widgetForm.value;
this.dashboardService.saveWidget(widgetData).subscribe(() => { if (this.isNew) {
this.router.navigate(['/dpu', this.appName, 'widget-warehouse']); // this.store.dispatch(DashboardActions.addWidget({ widget: widgetData }));
}); } else {
// this.store.dispatch(DashboardActions.updateWidget({ widget: widgetData }));
}
this.router.navigate(['/dpu/widget-management']);
} }
cancel(): void { cancel(): void {
......
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
import { Component, OnInit, Type } from '@angular/core'; import { Component, OnInit, Type } from '@angular/core';
import { ActivatedRoute, Router, RouterModule } from '@angular/router'; import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Widget } from '../../shared/models/dashboard.model';
import { DashboardService } from '../../shared/services/dashboard.service';
import { CommonModule, TitleCasePipe } from '@angular/common'; import { CommonModule, TitleCasePipe } from '@angular/common';
import { NgComponentOutlet } from '@angular/common'; import { NgComponentOutlet } from '@angular/common';
import { Store } from '@ngrx/store';
import * as DashboardActions from '../state/dashboard.actions';
import * as DashboardSelectors from '../state/dashboard.selectors';
import { WidgetModel } from '../models/widgets.model';
// Import all the widget components // Import all the widget components
import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component'; import { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
...@@ -28,7 +30,7 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg ...@@ -28,7 +30,7 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg
templateUrl: './widget-list.component.html', templateUrl: './widget-list.component.html',
}) })
export class WidgetListComponent implements OnInit { export class WidgetListComponent implements OnInit {
widgets$!: Observable<Widget[]>; widgets$!: Observable<WidgetModel[]>;
appName: string = ''; appName: string = '';
// Map string names to actual component classes // Map string names to actual component classes
...@@ -48,19 +50,14 @@ export class WidgetListComponent implements OnInit { ...@@ -48,19 +50,14 @@ export class WidgetListComponent implements OnInit {
}; };
constructor( constructor(
private dashboardService: DashboardService, private store: Store,
private route: ActivatedRoute, private route: ActivatedRoute,
private router: Router private router: Router
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.appName = this.route.snapshot.paramMap.get('appName') || ''; this.store.dispatch(DashboardActions.loadWidgets());
if (this.appName) { this.widgets$ = this.store.select(DashboardSelectors.selectAllWidgets);
this.widgets$ = this.dashboardService.getWidgets(this.appName);
} else {
// Handle case where appName is not in the URL
console.error('App name is missing from the route.');
}
} }
getComponentType(componentName: string): Type<any> | null { getComponentType(componentName: string): Type<any> | null {
......
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit, OnChanges, SimpleChanges, ChangeDetectorRef } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { GridModule, PageService, SortService, FilterService, GroupService } from '@syncfusion/ej2-angular-grids'; import { GridModule, PageService, SortService, FilterService, GroupService } from '@syncfusion/ej2-angular-grids';
import { DatasetService } from '../../services/dataset.service';
import { DatasetModel } from '../../models/widgets.model';
@Component({ @Component({
selector: 'app-syncfusion-datagrid-widget', selector: 'app-syncfusion-datagrid-widget',
...@@ -9,44 +13,37 @@ import { GridModule, PageService, SortService, FilterService, GroupService } fro ...@@ -9,44 +13,37 @@ import { GridModule, PageService, SortService, FilterService, GroupService } fro
providers: [PageService, SortService, FilterService, GroupService], providers: [PageService, SortService, FilterService, GroupService],
templateUrl: './syncfusion-datagrid-widget.component.html', templateUrl: './syncfusion-datagrid-widget.component.html',
}) })
export class SyncfusionDatagridWidgetComponent implements OnInit { export class SyncfusionDatagridWidgetComponent implements OnInit, OnChanges {
@Input() title: string = 'Data Grid'; // New input for title @Input() title: string = 'Data Grid';
@Input() gridData: object[] | undefined; // New input for grid data @Input() datasetId: string;
public data: object[] = []; public data: object[] = [];
public pageSettings: Object = { pageSize: 6 }; public pageSettings: Object = { pageSize: 6 };
constructor(private datasetService: DatasetService, private http: HttpClient, private cdr: ChangeDetectorRef) {}
ngOnInit(): void { ngOnInit(): void {
this.data = this.gridData || [ this.loadData();
{ OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, ShipCity: 'Reims' }, }
{ OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, ShipCity: 'Münster' },
{ OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, ShipCity: 'Rio de Janeiro' }, ngOnChanges(changes: SimpleChanges): void {
{ OrderID: 10251, CustomerID: 'VICTE', EmployeeID: 3, ShipCity: 'Lyon' }, if (changes['datasetId']) {
{ OrderID: 10252, CustomerID: 'SUPRD', EmployeeID: 2, ShipCity: 'Charleroi' }, this.loadData();
{ OrderID: 10253, CustomerID: 'HANAR', EmployeeID: 7, ShipCity: 'Rio de Janeiro' }, }
{ OrderID: 10254, CustomerID: 'CHOPS', EmployeeID: 8, ShipCity: 'Bern' }, }
{ OrderID: 10255, CustomerID: 'RICSU', EmployeeID: 9, ShipCity: 'Genève' },
{ OrderID: 10256, CustomerID: 'WELLI', EmployeeID: 1, ShipCity: 'Resende' }, loadData(): void {
{ OrderID: 10257, CustomerID: 'HILAA', EmployeeID: 2, ShipCity: 'Caracas' }, if (this.datasetId) {
{ OrderID: 10258, CustomerID: 'ERNSH', EmployeeID: 3, ShipCity: 'Graz' }, this.datasetService.getDatasetById(this.datasetId).subscribe((dataset: DatasetModel | undefined) => {
{ OrderID: 10259, CustomerID: 'CENTC', EmployeeID: 4, ShipCity: 'Madrid' }, if (dataset && dataset.url) {
{ OrderID: 10260, CustomerID: 'OTTIK', EmployeeID: 5, ShipCity: 'London' }, this.http.get<object[]>(dataset.url).subscribe(data => {
{ OrderID: 10261, CustomerID: 'QUEDE', EmployeeID: 6, ShipCity: 'Rio de Janeiro' }, this.data = data;
{ OrderID: 10262, CustomerID: 'RATTC', EmployeeID: 7, ShipCity: 'Madrid' }, this.cdr.markForCheck();
{ OrderID: 10263, CustomerID: 'ERNSH', EmployeeID: 8, ShipCity: 'Graz' }, });
{ OrderID: 10264, CustomerID: 'FOLIG', EmployeeID: 9, ShipCity: 'Liège' }, }
{ OrderID: 10265, CustomerID: 'BLONP', EmployeeID: 1, ShipCity: 'Marseille' }, });
{ OrderID: 10266, CustomerID: 'WARTH', EmployeeID: 2, ShipCity: 'Frankfurt a.M.' }, } else {
{ OrderID: 10267, CustomerID: 'FRANS', EmployeeID: 3, ShipCity: 'Torino' }, this.data = [];
{ OrderID: 10268, CustomerID: 'GROSR', EmployeeID: 4, ShipCity: 'London' }, }
{ OrderID: 10269, CustomerID: 'WHITC', EmployeeID: 5, ShipCity: 'Seattle' },
{ OrderID: 10270, CustomerID: 'VAFFE', EmployeeID: 6, ShipCity: 'Oslo' },
{ OrderID: 10271, CustomerID: 'BLAUS', EmployeeID: 7, ShipCity: 'Mannheim' },
{ OrderID: 10272, CustomerID: 'BLONP', EmployeeID: 8, ShipCity: 'Marseille' },
{ OrderID: 10273, CustomerID: 'WARTH', EmployeeID: 9, ShipCity: 'Frankfurt a.M.' },
{ OrderID: 10274, CustomerID: 'FRANS', EmployeeID: 1, ShipCity: 'Torino' },
{ OrderID: 10275, CustomerID: 'GROSR', EmployeeID: 2, ShipCity: 'London' },
{ OrderID: 10276, CustomerID: 'WHITC', EmployeeID: 3, ShipCity: 'Seattle' },
];
} }
} }
...@@ -20,6 +20,13 @@ import { HttpRequestInterceptor } from './shared/services/http-request.intercept ...@@ -20,6 +20,13 @@ import { HttpRequestInterceptor } from './shared/services/http-request.intercept
import { AuthService } from './shared/services/auth.service'; import { AuthService } from './shared/services/auth.service';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { MockDataInterceptor } from './shared/interceptors/mock-data.interceptor';
import { DashboardDataService } from './shared/services/dashboard-data.service';
import { provideStore, provideState } from '@ngrx/store';
import { provideEffects } from '@ngrx/effects';
import { provideStoreDevtools } from '@ngrx/store-devtools';
import { dashboardReducer } from './DPU/state/dashboard.reducer';
import { DashboardEffects } from './DPU/state/dashboard.effects';
export function HttpLoaderFactory(http: HttpClient) { export function HttpLoaderFactory(http: HttpClient) {
return new TranslateHttpLoader(http, "./assets/i18n/", ".json"); return new TranslateHttpLoader(http, "./assets/i18n/", ".json");
...@@ -39,25 +46,51 @@ export const httpInterceptorProviders = [ ...@@ -39,25 +46,51 @@ export const httpInterceptorProviders = [
provide: HTTP_INTERCEPTORS, provide: HTTP_INTERCEPTORS,
useClass: HttpRequestInterceptor, useClass: HttpRequestInterceptor,
multi: true, multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MockDataInterceptor,
multi: true,
} }
]; ];
export const appConfig: ApplicationConfig = { export const appConfig: ApplicationConfig = {
providers: [provideHttpClient(withInterceptors([])), provideRouter(App_Route), RouterOutlet, ColorPickerModule, ColorPickerService, provideAnimations(), AngularFireModule, providers: [
provideHttpClient(withInterceptors([])),
provideRouter(App_Route),
RouterOutlet,
ColorPickerModule,
ColorPickerService,
provideAnimations(),
AngularFireModule,
AngularFireDatabaseModule, AngularFireDatabaseModule,
AngularFirestoreModule, AngularFirestoreModule,
AngularFireAuthModule, AngularFireAuthModule,
importProvidersFrom(RouterModule.forRoot(App_Route, { initialNavigation: 'enabledNonBlocking', useHash: true }), TranslateModule.forRoot(provideTranslation()), CalendarModule.forRoot({ importProvidersFrom(
provide: DateAdapter, RouterModule.forRoot(App_Route, { initialNavigation: 'enabledNonBlocking', useHash: true }),
useFactory: adapterFactory, TranslateModule.forRoot(provideTranslation()),
}), ToastrModule.forRoot({ CalendarModule.forRoot({
timeOut: 15000, // 15 seconds provide: DateAdapter,
closeButton: true, useFactory: adapterFactory,
progressBar: true, }),
}), NgDragDropModule.forRoot(), HttpClientModule), ToastrModule.forRoot({
timeOut: 15000, // 15 seconds
closeButton: true,
progressBar: true,
}),
NgDragDropModule.forRoot(),
HttpClientModule
),
httpInterceptorProviders, httpInterceptorProviders,
DashboardDataService,
provideStore(),
provideState({ name: 'dashboard', reducer: dashboardReducer }),
provideEffects([DashboardEffects]),
provideStoreDevtools({ maxAge: 25, logOnly: environment.production })
] ]
}; };
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpResponse } from '@angular/common/http';
import { Observable, of, switchMap } from 'rxjs';
import { DatasetService } from '../../DPU/services/dataset.service';
@Injectable()
export class MockDataInterceptor implements HttpInterceptor {
constructor(private datasetService: DatasetService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (request.url.startsWith('/api/data/')) {
const datasetId = request.url.split('/').pop();
if (!datasetId) {
return of(new HttpResponse({ status: 404, body: [] }));
}
return this.datasetService.getDatasetById(datasetId).pipe(
switchMap((dataset: any) => {
if (dataset && dataset.url) {
// In a real app, you would fetch the data from the URL.
// For this mock, we'll just return an empty array.
return of(new HttpResponse({ status: 200, body: [] }));
}
return of(new HttpResponse({ status: 404, body: [] }));
})
);
}
return next.handle(request);
}
}
import { Injectable } from '@angular/core';
import { Observable, of, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { DashboardModel, WidgetModel, DatasetModel } from '../../DPU/models/widgets.model';
@Injectable({
providedIn: 'root'
})
export class DashboardDataService {
private mockDatasets: DatasetModel[] = [
new DatasetModel({ id: 'ds-1', name: 'Sales Data', url: '/api/data/sales' }),
new DatasetModel({ id: 'ds-2', name: 'HR Data', url: '/api/data/hr' }),
new DatasetModel({ id: 'ds-3', name: 'Marketing Data', url: '/api/data/marketing' })
];
private mockData: { [key: string]: any[] } = {
'ds-1': [
{ OrderID: 10248, CustomerID: 'VINET', EmployeeID: 5, OrderDate: new Date(8364186e5), ShipName: 'Vins et alcools Chevalier' },
{ OrderID: 10249, CustomerID: 'TOMSP', EmployeeID: 6, OrderDate: new Date(836505e6), ShipName: 'Toms Spezialitäten' },
{ OrderID: 10250, CustomerID: 'HANAR', EmployeeID: 4, OrderDate: new Date(8367642e5), ShipName: 'Hanari Carnes' },
],
'ds-2': [
{ Name: 'John Doe', Title: 'CEO', Country: 'USA' },
{ Name: 'Jane Smith', Title: 'CFO', Country: 'USA' },
{ Name: 'Peter Jones', Title: 'CTO', Country: 'UK' },
]
};
private mockDashboards: DashboardModel[] = [
new DashboardModel({
id: 'dash-1',
name: 'Sales Dashboard',
datasetId: 'ds-1',
widgets: [
new WidgetModel({ id: 'widget-1', name: 'Welcome Message', component: 'WelcomeWidgetComponent', cols: 2, rows: 1, y: 0, x: 0, data: { userName: 'Jane Doe' } }),
new WidgetModel({ id: 'widget-2', name: 'Sales Data Grid', component: 'SyncfusionDatagridWidgetComponent', cols: 4, rows: 3, y: 0, x: 2, data: {} })
]
}),
new DashboardModel({
id: 'dash-2',
name: 'HR Dashboard',
datasetId: 'ds-2',
widgets: [
new WidgetModel({ id: 'widget-1', name: 'Welcome Message', component: 'WelcomeWidgetComponent', cols: 2, rows: 1, y: 0, x: 0, data: { userName: 'John Smith' } })
]
})
];
private mockWidgets: WidgetModel[] = [
new WidgetModel({ id: 'widget-1', name: 'Welcome Message', component: 'WelcomeWidgetComponent', cols: 2, rows: 1, y: 0, x: 0, data: { userName: 'Default User' } }),
new WidgetModel({ id: 'widget-2', name: 'Sales Data Grid', component: 'SyncfusionDatagridWidgetComponent', cols: 4, rows: 3, y: 0, x: 2, data: {} }),
new WidgetModel({ id: 'widget-3', name: 'Chart Widget', component: 'ChartWidgetComponent', cols: 3, rows: 2, y: 0, x: 0, data: {} })
];
constructor() { }
// Dashboard methods
getDashboards(): Observable<DashboardModel[]> {
return of(this.mockDashboards).pipe(catchError(this.handleError));
}
getDashboardById(id: string): Observable<DashboardModel | undefined> {
return of(this.mockDashboards.find(d => d.id === id)).pipe(catchError(this.handleError));
}
addDashboard(dashboard: DashboardModel): Observable<DashboardModel> {
this.mockDashboards.push(dashboard);
return of(dashboard).pipe(catchError(this.handleError));
}
updateDashboard(dashboard: DashboardModel): Observable<DashboardModel> {
const index = this.mockDashboards.findIndex(d => d.id === dashboard.id);
if (index !== -1) {
this.mockDashboards[index] = dashboard;
}
return of(dashboard).pipe(catchError(this.handleError));
}
deleteDashboard(id: string): Observable<void> {
this.mockDashboards = this.mockDashboards.filter(d => d.id !== id);
return of(undefined).pipe(catchError(this.handleError));
}
// Widget methods
getWidgets(): Observable<WidgetModel[]> {
return of(this.mockWidgets).pipe(catchError(this.handleError));
}
// Dataset methods
getDatasets(): Observable<DatasetModel[]> {
return of(this.mockDatasets).pipe(catchError(this.handleError));
}
getDatasetById(id: string): Observable<DatasetModel | undefined> {
return of(this.mockDatasets.find(ds => ds.id === id)).pipe(catchError(this.handleError));
}
private handleError(error: any) {
console.error(error);
return throwError(() => new Error('Something went wrong. Please try again later.'));
}
}
[{"id": 1, "name": "John Doe", "age": 30},{"id": 2, "name": "Jane Smith", "age": 25}]
\ No newline at end of file
[{"product": "Laptop", "price": 1200},{"product": "Mouse", "price": 25}]
\ No newline at end of file
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