|
|
@@ -13,12 +13,16 @@ import { MatNativeDateModule } from '@angular/material/core';
|
|
|
import { ReactiveFormsModule, FormControl } from '@angular/forms';
|
|
|
import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
|
|
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
|
|
+import { MatPaginatorModule, PageEvent } from '@angular/material/paginator';
|
|
|
+import { MatTooltipModule } from '@angular/material/tooltip';
|
|
|
import { combineLatest, startWith } from 'rxjs';
|
|
|
import { FFBProduction } from './ffb-production.interface';
|
|
|
import { webConfig } from '../config';
|
|
|
import { CreateFfbProductionDialogComponent } from '../components/ffb-production-dialog/create-ffb-production-dialog.component';
|
|
|
import { FfbHarvestCalculateDialogComponent } from '../components/ffb-calculation/ffb-production-calculate-dialog.component';
|
|
|
import { FfbVectorSearchDialogComponent } from '../components/ffb-vector-search-dialog/ffb-vector-search-dialog.component';
|
|
|
+import { FfbChatDialogComponent } from '../components/ffb-chat-dialog/ffb-chat-dialog.component';
|
|
|
+import { BulkCreateFfbDialogComponent } from '../components/bulk-create-ffb-dialog/bulk-create-ffb-dialog.component';
|
|
|
|
|
|
@Component({
|
|
|
selector: 'app-ffb-production',
|
|
|
@@ -39,7 +43,9 @@ import { FfbVectorSearchDialogComponent } from '../components/ffb-vector-search-
|
|
|
MatNativeDateModule,
|
|
|
ReactiveFormsModule,
|
|
|
MatSnackBarModule,
|
|
|
- MatDialogModule
|
|
|
+ MatDialogModule,
|
|
|
+ MatTooltipModule,
|
|
|
+ MatPaginatorModule
|
|
|
],
|
|
|
})
|
|
|
export class FfbProductionComponent implements OnInit {
|
|
|
@@ -76,6 +82,12 @@ export class FfbProductionComponent implements OnInit {
|
|
|
'actions',
|
|
|
];
|
|
|
|
|
|
+ // Pagination state
|
|
|
+ totalItems = 0;
|
|
|
+ pageSize = 10;
|
|
|
+ pageIndex = 0;
|
|
|
+ pageSizeOptions = [5, 10, 25, 50];
|
|
|
+
|
|
|
loading = false;
|
|
|
|
|
|
ngOnInit() {
|
|
|
@@ -94,14 +106,58 @@ export class FfbProductionComponent implements OnInit {
|
|
|
|
|
|
loadFFBproduction() {
|
|
|
this.loading = true;
|
|
|
- this.http.get<FFBProduction[]>(`${webConfig.exposedUrl}/api/ffb-production`).subscribe({
|
|
|
- next: (data) => {
|
|
|
+
|
|
|
+ // Prepare query params
|
|
|
+ const params: any = {
|
|
|
+ page: this.pageIndex + 1, // backend is 1-indexed probably
|
|
|
+ limit: this.pageSize,
|
|
|
+ };
|
|
|
+
|
|
|
+ // Add filters if present
|
|
|
+ const keyword = (this.searchControl.value || '').trim();
|
|
|
+ if (keyword) params.keyword = keyword;
|
|
|
+
|
|
|
+ if (this.siteControl.value) params.site = this.siteControl.value;
|
|
|
+ if (this.phaseControl.value) params.phase = this.phaseControl.value;
|
|
|
+
|
|
|
+ const startDate = this.startDateControl.value;
|
|
|
+ if (startDate) params.startDate = startDate.toISOString();
|
|
|
+
|
|
|
+ const endDate = this.endDateControl.value;
|
|
|
+ if (endDate) params.endDate = endDate.toISOString();
|
|
|
+
|
|
|
+ // UOM filters
|
|
|
+ if (this.weightUomControl.value) params.weightUom = this.weightUomControl.value;
|
|
|
+ if (this.quantityUomControl.value) params.quantityUom = this.quantityUomControl.value;
|
|
|
+
|
|
|
+ this.http.get<{ data: FFBProduction[], total: number }>(`${webConfig.exposedUrl}/api/ffb-production`, { params }).subscribe({
|
|
|
+ next: (response) => {
|
|
|
+ // Handle paginated response
|
|
|
+ // If backend returns { data: [], total: 0 }, construct based on that.
|
|
|
+ // If actual structure is just [], we need to adjust.
|
|
|
+ // Assuming user confirmed standard structure or I will use flexible handling.
|
|
|
+
|
|
|
+ let data: FFBProduction[] = [];
|
|
|
+ if (Array.isArray(response)) {
|
|
|
+ // Fallback if backend just returns array
|
|
|
+ data = response;
|
|
|
+ this.totalItems = data.length;
|
|
|
+ } else {
|
|
|
+ data = response.data || [];
|
|
|
+ this.totalItems = response.total || 0;
|
|
|
+ }
|
|
|
+
|
|
|
this.production = data.map(h => ({ ...h, productionDate: new Date(h.productionDate) }));
|
|
|
+ // filteredProductions is now just the current page data
|
|
|
this.filteredProductions = [...this.production];
|
|
|
- // populate unique arrays for filters and autocompletes
|
|
|
+
|
|
|
+ // populate unique arrays for filters and autocompletes - this might need a separate API call if we want ALL unique values
|
|
|
+ // For now, we only get unique values from CURRENT PAGE, which is a limitation of server-side pagination without separate metadata endpoint.
|
|
|
+ // Keeping existing logic for now, but be aware of this limitation.
|
|
|
this.uniqueSites = [...new Set(this.production.map(h => h.site))];
|
|
|
this.uniquePhases = [...new Set(this.production.map(h => h.phase))];
|
|
|
this.uniqueBlocks = [...new Set(this.production.map(h => h.block))];
|
|
|
+
|
|
|
this.loading = false;
|
|
|
},
|
|
|
error: (err) => {
|
|
|
@@ -112,36 +168,16 @@ export class FfbProductionComponent implements OnInit {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ onPageChange(event: PageEvent) {
|
|
|
+ this.pageIndex = event.pageIndex;
|
|
|
+ this.pageSize = event.pageSize;
|
|
|
+ this.loadFFBproduction();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Modified to just trigger reload since filtering is server-side
|
|
|
applyFilters() {
|
|
|
- const keyword = (this.searchControl.value || '').toLowerCase().trim();
|
|
|
- const site = this.siteControl.value;
|
|
|
- const phase = this.phaseControl.value;
|
|
|
- const startDate = this.startDateControl.value;
|
|
|
- const endDate = this.endDateControl.value;
|
|
|
- const weightUom = this.weightUomControl.value;
|
|
|
- const quantityUom = this.quantityUomControl.value;
|
|
|
-
|
|
|
- this.filteredProductions = this.production.filter(h => {
|
|
|
- const matchesKeyword =
|
|
|
- !keyword ||
|
|
|
- h.site.toLowerCase().includes(keyword) ||
|
|
|
- h.phase.toLowerCase().includes(keyword) ||
|
|
|
- h.block.toLowerCase().includes(keyword) ||
|
|
|
- h.weight.toString().includes(keyword) ||
|
|
|
- h.quantity.toString().includes(keyword);
|
|
|
-
|
|
|
- const matchesSite = !site || h.site === site;
|
|
|
- const matchesPhase = !phase || h.phase === phase;
|
|
|
- const matchesWeightUom = !weightUom || h.weightUom === weightUom;
|
|
|
- const matchesQuantityUom = !quantityUom || h.quantityUom === quantityUom;
|
|
|
-
|
|
|
- const hDate = new Date(h.productionDate);
|
|
|
- const matchesDate =
|
|
|
- (!startDate || hDate >= startDate) && (!endDate || hDate <= endDate);
|
|
|
-
|
|
|
- return matchesKeyword && matchesSite && matchesPhase &&
|
|
|
- matchesWeightUom && matchesQuantityUom && matchesDate;
|
|
|
- });
|
|
|
+ this.pageIndex = 0; // Reset to first page on filter change
|
|
|
+ this.loadFFBproduction();
|
|
|
}
|
|
|
|
|
|
resetFilters() {
|
|
|
@@ -152,7 +188,8 @@ export class FfbProductionComponent implements OnInit {
|
|
|
this.endDateControl.setValue(null);
|
|
|
this.weightUomControl.setValue('');
|
|
|
this.quantityUomControl.setValue('');
|
|
|
- this.filteredProductions = [...this.production];
|
|
|
+ this.pageIndex = 0;
|
|
|
+ this.loadFFBproduction();
|
|
|
}
|
|
|
|
|
|
refresh() {
|
|
|
@@ -237,4 +274,30 @@ export class FfbProductionComponent implements OnInit {
|
|
|
});
|
|
|
}
|
|
|
|
|
|
+ openChatDialog() {
|
|
|
+ this.dialog.open(FfbChatDialogComponent, {
|
|
|
+ width: '600px',
|
|
|
+ height: '80vh',
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ openBulkCreateDialog() {
|
|
|
+ const dialogRef = this.dialog.open(BulkCreateFfbDialogComponent, {
|
|
|
+ width: '600px',
|
|
|
+ disableClose: true, // Prevent closing while processing
|
|
|
+ data: {
|
|
|
+ allSites: this.uniqueSites,
|
|
|
+ allPhases: this.uniquePhases,
|
|
|
+ allBlocks: this.uniqueBlocks,
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ dialogRef.afterClosed().subscribe(result => {
|
|
|
+ if (result === 'refresh') this.loadFFBproduction();
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
}
|