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 @@
"@ks89/angular-modal-gallery": "^11.1.1",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@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/http-loader": "^8.0.0",
"@syncfusion/ej2-angular-base": "^29.2.4",
......
......@@ -104,11 +104,6 @@ export const admin: Routes = [
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',
loadComponent: () =>
import('./myportal/excel-report/excel-report.component').then((m) => m.ExcelReportComponent),
......@@ -134,11 +129,6 @@ export const admin: Routes = [
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',
loadComponent: () =>
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">
<!-- Widget Sidebar -->
<div class="w-72 bg-white p-4 overflow-y-auto shadow-lg border-r flex flex-col">
......@@ -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">
<option *ngFor="let dash of userDashboards" [value]="dash.id">{{ dash.name }}</option>
</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()"
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">
<i class="bi bi-check-circle-fill text-2xl"></i>
</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>
<div class="control-section">
<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 { ActivatedRoute, RouterModule } from '@angular/router';
import { DashboardLayoutModule, PanelModel } from '@syncfusion/ej2-angular-layouts';
import { DashboardService } from '../../shared/services/dashboard.service';
import { DashboardLayout, Widget } from '../../shared/models/dashboard.model';
import { Store } from '@ngrx/store';
import { map, switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
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 { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
......@@ -29,7 +31,8 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg
standalone: true,
imports: [CommonModule, RouterModule, DashboardLayoutModule, NgComponentOutlet, CompanyInfoWidgetComponent, HeadcountWidgetComponent, AttendanceOverviewWidgetComponent, PayrollSummaryWidgetComponent, EmployeeDirectoryWidgetComponent, KpiWidgetComponent, WelcomeWidgetComponent, ChartWidgetComponent, QuickLinksWidgetComponent, SyncfusionDatagridWidgetComponent, SyncfusionPivotWidgetComponent, SyncfusionChartWidgetComponent],
templateUrl: './dashboard-viewer.component.html',
styleUrls: ['./dashboard-viewer.component.scss']
styleUrls: ['./dashboard-viewer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardViewerComponent implements OnInit {
......@@ -54,9 +57,11 @@ export class DashboardViewerComponent implements OnInit {
'SyncfusionChartWidgetComponent': SyncfusionChartWidgetComponent
};
public errorMessage: string | null = null;
constructor(
private route: ActivatedRoute,
private dashboardService: DashboardService
private store: Store
) { }
ngOnInit(): void {
......@@ -67,19 +72,22 @@ export class DashboardViewerComponent implements OnInit {
console.error('Dashboard ID is missing from the route.');
return of(null);
}
return this.dashboardService.getDashboardLayout(id);
this.store.dispatch(DashboardActions.loadDashboards());
return this.store.select(DashboardSelectors.selectDashboardById(id));
})
).subscribe(dashboard => {
if (dashboard) {
this.loadDashboard(dashboard);
}
).subscribe({
next: dashboard => {
if (dashboard) {
this.loadDashboard(dashboard);
}
},
error: err => this.errorMessage = err.message
});
}
loadDashboard(dashboard: DashboardLayout): void {
loadDashboard(dashboard: DashboardModel): void {
this.dashboardName = dashboard.name;
// Map widgets from the layout to Syncfusion PanelModels, attaching componentType
this.panels = dashboard.widgets.map((widget: Widget) => ({
this.panels = dashboard.widgets.map(widget => ({
id: widget.id,
row: widget.y,
col: widget.x,
......@@ -87,7 +95,7 @@ export class DashboardViewerComponent implements OnInit {
sizeY: widget.rows,
header: widget.name,
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";
import { TagModel } from "./tag.mmodel"
import { GroupModel } from "./group.mmodel"
export interface WidgetModel {
widgetId: string
widgetTname: string
widgetEname: 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 interface IDataset {
id: string;
name: string;
url: string;
}
export class WidgetModel implements WidgetModel {
widgetId: string
widgetTname: string
widgetEname: 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 DatasetModel implements IDataset {
id: string;
name: string;
url: string;
constructor(data: Partial<WidgetModel>) {
this.widgetId = data.widgetId ?? ''
this.widgetTname = data.widgetTname ?? ''
this.widgetEname = data.widgetEname ?? ''
this.thDesc = data.thDesc ?? ''
this.engDesc = data.engDesc ?? ''
this.chartType = data.chartType ?? ''
this.widgetHeight = data.widgetHeight ?? 0
this.widgetWidth = data.widgetWidth ?? 0
this.status = data.status ?? 0
this.seriesColumn = data.seriesColumn ?? ''
this.picture = data.picture ?? ''
this.remark = data.remark ?? ''
this.link1 = data.link1 ?? ''
this.widgetObj = data.widgetObj ?? ''
this.dbSupport = data.dbSupport ?? ''
this.tags = data.tags ?? []
this.fileType = data.fileType ?? ''
this.isPivot = data.isPivot ?? 0
this.isDataGrid = data.isDataGrid ?? 0
this.uploadBy = data.uploadBy ?? ''
this.uploadDate = data.uploadDate ?? ''
this.uploadTime = data.uploadTime ?? ''
this.group = data.group ? new GroupModel(data.group) : new GroupModel({})
}
constructor(data: Partial<DatasetModel>) {
this.id = data.id ?? '';
this.name = data.name ?? '';
this.url = data.url ?? '';
}
}
export interface IWidget {
id: string;
name: string;
component: string;
cols: number;
rows: number;
x: number;
y: number;
data: any;
}
export class WidgetModel implements IWidget {
id: string;
name: string;
component: string;
cols: number;
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 {
if (this.picture) {
return environment.url + "files/image/" + this.picture
} else {
return 'assets/images/big/auth-bg.jpg'
}
}
}
\ No newline at end of file
export class DashboardModel implements IDashboard {
id: string;
name: string;
description: string;
datasetId?: string;
widgets: IWidget[];
constructor(data: Partial<IDashboard>) {
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 = [
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',
loadComponent: () =>
import('../myskill-x/myportal/excel-report/excel-report.component').then((m) => m.ExcelReportComponent),
......@@ -60,11 +55,6 @@ export const myportal: Routes = [
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',
loadComponent: () =>
import('../myskill-x/myportal/set-excel-reports/excel-list/excel-list.component').then((m) => m.ExcelListComponent),
......@@ -154,4 +144,4 @@ export const myportal: Routes = [
})
export class MyskillXModule {
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 {
}
deleteWidget(model: WidgetModel): Observable<any> {
let body = {
widgetId: model.widgetId
widgetId: model.id
}
let option = {
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 @@
import { Component, OnInit, Type } from '@angular/core';
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
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 { 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 { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
......@@ -53,13 +54,12 @@ export class WidgetFormComponent implements OnInit {
constructor(
private fb: FormBuilder,
private dashboardService: DashboardService,
private store: Store,
private route: ActivatedRoute,
private router: Router
) { }
ngOnInit(): void {
this.appName = this.route.snapshot.paramMap.get('appName') || '';
this.widgetId = this.route.snapshot.paramMap.get('widgetId');
this.widgetForm = this.fb.group({
......@@ -69,17 +69,19 @@ export class WidgetFormComponent implements OnInit {
cols: [1, [Validators.required, Validators.min(1)]],
rows: [1, [Validators.required, Validators.min(1)]],
x: [0],
y: [0]
y: [0],
data: [null]
});
if (this.widgetId && this.widgetId !== 'new') {
this.isNew = false;
this.dashboardService.getWidget(this.widgetId).subscribe(widget => {
if (widget) {
this.widgetForm.patchValue(widget);
this.updatePreview(widget.component);
}
});
this.store.dispatch(DashboardActions.loadWidgets());
// this.store.select(DashboardSelectors.selectWidgetById(this.widgetId)).subscribe(widget => {
// if (widget) {
// this.widgetForm.patchValue(widget);
// this.updatePreview(widget.component);
// }
// });
}
// Subscribe to component name changes for live preview
......@@ -97,10 +99,13 @@ export class WidgetFormComponent implements OnInit {
return;
}
const widgetData: Widget = this.widgetForm.value;
this.dashboardService.saveWidget(widgetData).subscribe(() => {
this.router.navigate(['/dpu', this.appName, 'widget-warehouse']);
});
const widgetData: WidgetModel = this.widgetForm.value;
if (this.isNew) {
// this.store.dispatch(DashboardActions.addWidget({ widget: widgetData }));
} else {
// this.store.dispatch(DashboardActions.updateWidget({ widget: widgetData }));
}
this.router.navigate(['/dpu/widget-management']);
}
cancel(): void {
......
......@@ -2,10 +2,12 @@
import { Component, OnInit, Type } from '@angular/core';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
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 { 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 { CompanyInfoWidgetComponent } from '../widgets/company-info-widget.component';
......@@ -28,7 +30,7 @@ import { SyncfusionChartWidgetComponent } from '../widgets/syncfusion-chart-widg
templateUrl: './widget-list.component.html',
})
export class WidgetListComponent implements OnInit {
widgets$!: Observable<Widget[]>;
widgets$!: Observable<WidgetModel[]>;
appName: string = '';
// Map string names to actual component classes
......@@ -48,19 +50,14 @@ export class WidgetListComponent implements OnInit {
};
constructor(
private dashboardService: DashboardService,
private store: Store,
private route: ActivatedRoute,
private router: Router
) { }
ngOnInit(): void {
this.appName = this.route.snapshot.paramMap.get('appName') || '';
if (this.appName) {
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.');
}
this.store.dispatch(DashboardActions.loadWidgets());
this.widgets$ = this.store.select(DashboardSelectors.selectAllWidgets);
}
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 { HttpClient } from '@angular/common/http';
import { GridModule, PageService, SortService, FilterService, GroupService } from '@syncfusion/ej2-angular-grids';
import { DatasetService } from '../../services/dataset.service';
import { DatasetModel } from '../../models/widgets.model';
@Component({
selector: 'app-syncfusion-datagrid-widget',
......@@ -9,44 +13,37 @@ import { GridModule, PageService, SortService, FilterService, GroupService } fro
providers: [PageService, SortService, FilterService, GroupService],
templateUrl: './syncfusion-datagrid-widget.component.html',
})
export class SyncfusionDatagridWidgetComponent implements OnInit {
@Input() title: string = 'Data Grid'; // New input for title
@Input() gridData: object[] | undefined; // New input for grid data
export class SyncfusionDatagridWidgetComponent implements OnInit, OnChanges {
@Input() title: string = 'Data Grid';
@Input() datasetId: string;
public data: object[] = [];
public pageSettings: Object = { pageSize: 6 };
constructor(private datasetService: DatasetService, private http: HttpClient, private cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this.data = this.gridData || [
{ 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' },
{ OrderID: 10251, CustomerID: 'VICTE', EmployeeID: 3, ShipCity: 'Lyon' },
{ OrderID: 10252, CustomerID: 'SUPRD', EmployeeID: 2, ShipCity: 'Charleroi' },
{ 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' },
{ OrderID: 10257, CustomerID: 'HILAA', EmployeeID: 2, ShipCity: 'Caracas' },
{ OrderID: 10258, CustomerID: 'ERNSH', EmployeeID: 3, ShipCity: 'Graz' },
{ OrderID: 10259, CustomerID: 'CENTC', EmployeeID: 4, ShipCity: 'Madrid' },
{ OrderID: 10260, CustomerID: 'OTTIK', EmployeeID: 5, ShipCity: 'London' },
{ OrderID: 10261, CustomerID: 'QUEDE', EmployeeID: 6, ShipCity: 'Rio de Janeiro' },
{ OrderID: 10262, CustomerID: 'RATTC', EmployeeID: 7, ShipCity: 'Madrid' },
{ 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.' },
{ OrderID: 10267, CustomerID: 'FRANS', EmployeeID: 3, ShipCity: 'Torino' },
{ 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' },
];
this.loadData();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['datasetId']) {
this.loadData();
}
}
loadData(): void {
if (this.datasetId) {
this.datasetService.getDatasetById(this.datasetId).subscribe((dataset: DatasetModel | undefined) => {
if (dataset && dataset.url) {
this.http.get<object[]>(dataset.url).subscribe(data => {
this.data = data;
this.cdr.markForCheck();
});
}
});
} else {
this.data = [];
}
}
}
......@@ -20,6 +20,13 @@ import { HttpRequestInterceptor } from './shared/services/http-request.intercept
import { AuthService } from './shared/services/auth.service';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
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) {
return new TranslateHttpLoader(http, "./assets/i18n/", ".json");
......@@ -39,25 +46,51 @@ export const httpInterceptorProviders = [
provide: HTTP_INTERCEPTORS,
useClass: HttpRequestInterceptor,
multi: true,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MockDataInterceptor,
multi: true,
}
];
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,
AngularFirestoreModule,
AngularFireAuthModule,
importProvidersFrom(RouterModule.forRoot(App_Route, { initialNavigation: 'enabledNonBlocking', useHash: true }), TranslateModule.forRoot(provideTranslation()), CalendarModule.forRoot({
provide: DateAdapter,
useFactory: adapterFactory,
}), ToastrModule.forRoot({
timeOut: 15000, // 15 seconds
closeButton: true,
progressBar: true,
}), NgDragDropModule.forRoot(), HttpClientModule),
importProvidersFrom(
RouterModule.forRoot(App_Route, { initialNavigation: 'enabledNonBlocking', useHash: true }),
TranslateModule.forRoot(provideTranslation()),
CalendarModule.forRoot({
provide: DateAdapter,
useFactory: adapterFactory,
}),
ToastrModule.forRoot({
timeOut: 15000, // 15 seconds
closeButton: true,
progressBar: true,
}),
NgDragDropModule.forRoot(),
HttpClientModule
),
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