Browse Source

feat: add FFB production interface and dialogs for single and bulk creation.

Dr-Swopt 2 weeks ago
parent
commit
4667d2ea9e

+ 6 - 0
src/app/components/bulk-create-ffb-dialog/bulk-create-ffb-dialog.component.html

@@ -38,6 +38,12 @@
             </mat-form-field>
         </div>
 
+        <div class="row" style="margin-bottom: 20px;">
+            <mat-slide-toggle formControlName="includeIssues" color="warn">
+                Generate Random Issues (Simulated)
+            </mat-slide-toggle>
+        </div>
+
         <div class="info-box">
             <mat-icon color="primary">info</mat-icon>
             <p>Quantity (Bundles) will be calculated as Weight / 10.</p>

+ 21 - 2
src/app/components/bulk-create-ffb-dialog/bulk-create-ffb-dialog.component.ts

@@ -10,6 +10,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
 import { MatProgressBarModule } from '@angular/material/progress-bar';
 import { MatDatepickerModule } from '@angular/material/datepicker';
 import { MatNativeDateModule } from '@angular/material/core';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
 import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
 import { HttpClient, HttpClientModule } from '@angular/common/http';
 import { webConfig } from '../../config';
@@ -33,6 +34,7 @@ import { SiteService } from '../../services/site-management.service';
         MatProgressBarModule,
         MatDatepickerModule,
         MatNativeDateModule,
+        MatSlideToggleModule,
         ReactiveFormsModule,
         HttpClientModule
     ]
@@ -68,7 +70,8 @@ export class BulkCreateFfbDialogComponent {
             startDate: [new Date('2025-01-01'), Validators.required],
             endDate: [new Date('2026-12-31'), Validators.required],
             minWeight: [100, [Validators.required, Validators.min(1)]],
-            maxWeight: [200, [Validators.required, Validators.min(1)]]
+            maxWeight: [200, [Validators.required, Validators.min(1)]],
+            includeIssues: [false]
         });
     }
 
@@ -154,6 +157,21 @@ export class BulkCreateFfbDialogComponent {
                 const remarkRes = await this.http.post<{ remark: string }>(`${webConfig.exposedUrl}/api/ffb-production/generate-remark`, context).toPromise();
                 const remark = remarkRes?.remark || 'No remark generated';
 
+                // 2b. Generate Issues (Optional 70% chance if enabled)
+                let issues = undefined;
+                if (config.includeIssues) {
+                    if (Math.random() < 0.7) {
+                        this.log(`[${i + 1}] Generating random issue...`);
+                        const issueRes = await this.http.post<{ issues: string }>(`${webConfig.exposedUrl}/api/ffb-production/generate-issues`, context).toPromise();
+                        issues = issueRes?.issues;
+                        if (issues) {
+                            this.log(`[${i + 1}] Issue: ${issues.substring(0, 30)}...`);
+                        }
+                    } else {
+                        this.log(`[${i + 1}] No issue for this record (random).`);
+                    }
+                }
+
                 // 3. Save Record
                 this.log(`[${i + 1}] Persisting record...`);
                 const payload = {
@@ -163,7 +181,8 @@ export class BulkCreateFfbDialogComponent {
                     block: { id: block._id, name: block.name },
                     quantity, quantityUom: 'Bundle',
                     weight, weightUom: 'Kg',
-                    remarks: remark
+                    remarks: remark,
+                    issues: issues
                 };
 
                 await this.http.post(`${webConfig.exposedUrl}/api/ffb-production`, payload).toPromise();

+ 18 - 9
src/app/components/ffb-production-dialog/create-ffb-production-dialog.component.html

@@ -58,17 +58,26 @@
       </mat-select>
     </mat-form-field>
 
-    <div class="remarks-container" style="width: 100%; display: flex; align-items: flex-start; gap: 10px;">
-      <mat-form-field appearance="outline" style="flex: 1;">
-        <mat-label>Remarks</mat-label>
+    <div class="remarks-container" style="width: 100%; display: flex; flex-direction: column; gap: 10px;">
+      <mat-form-field appearance="outline" style="width: 100%;">
+        <mat-label>Remarks (General Work Done)</mat-label>
         <textarea matInput formControlName="remarks" rows="3" placeholder="Add optional remarks..."></textarea>
+        <button mat-icon-button matSuffix (click)="generateRemark()"
+          [disabled]="saving || generatingRemark || form.get('remarks')?.disabled"
+          matTooltip="Generate General Remarks with AI">
+          <mat-icon [class.spin]="generatingRemark">auto_awesome</mat-icon>
+        </button>
+      </mat-form-field>
+
+      <mat-form-field appearance="outline" style="width: 100%;">
+        <mat-label>Issues (Optional)</mat-label>
+        <textarea matInput formControlName="issues" rows="2"
+          placeholder="Describe any problems or bottlenecks..."></textarea>
+        <button mat-icon-button matSuffix (click)="generateIssues()" [disabled]="saving || generatingIssues"
+          matTooltip="Generate Issues with AI">
+          <mat-icon [class.spin]="generatingIssues" color="warn">warning</mat-icon>
+        </button>
       </mat-form-field>
-      <button mat-mini-fab color="accent" type="button" (click)="generateRemark()" matTooltip="Generate Remark with AI"
-        [disabled]="saving || generatingRemark || form.get('remarks')?.disabled">
-        <mat-progress-spinner *ngIf="generatingRemark" mode="indeterminate" diameter="20"
-          color="primary"></mat-progress-spinner>
-        <mat-icon *ngIf="!generatingRemark">auto_awesome</mat-icon>
-      </button>
     </div>
 
   </div>

+ 42 - 3
src/app/components/ffb-production-dialog/create-ffb-production-dialog.component.ts

@@ -44,6 +44,7 @@ export class CreateFfbProductionDialogComponent implements OnInit {
   form: FormGroup;
   saving = false;
   generatingRemark = false;
+  generatingIssues = false;
 
   weightUomOptions = ['Kg', 'Ton'];
   quantityUomOptions = ['Bunch', 'Bag'];
@@ -71,7 +72,8 @@ export class CreateFfbProductionDialogComponent implements OnInit {
       quantityUom: ['Bunch', Validators.required],
       weight: [0, Validators.required],
       weightUom: ['Kg', Validators.required],
-      remarks: [{ value: '', disabled: true }]
+      remarks: [{ value: '', disabled: true }],
+      issues: ['']
     });
 
     // Monitor form changes to enable/disable remarks
@@ -100,7 +102,8 @@ export class CreateFfbProductionDialogComponent implements OnInit {
         quantityUom: harvest.quantityUom,
         weight: harvest.weight,
         weightUom: harvest.weightUom,
-        remarks: harvest.remarks
+        remarks: harvest.remarks,
+        issues: harvest.issues
       });
 
       // For dependent dropdowns, we need to load them sequentially
@@ -230,6 +233,41 @@ export class CreateFfbProductionDialogComponent implements OnInit {
     });
   }
 
+  generateIssues() {
+    if (this.generatingIssues) return;
+
+    this.generatingIssues = true;
+    const payload = this.form.value;
+
+    const context = {
+      siteId: payload.site?._id || payload.site?.id,
+      phaseId: payload.phase?._id || payload.phase?.id,
+      blockId: payload.block?._id || payload.block?.id,
+      site: payload.site?.name,
+      phase: payload.phase?.name,
+      block: payload.block?.name,
+      date: payload.productionDate,
+      quantity: payload.quantity,
+      quantityUom: payload.quantityUom,
+      weight: payload.weight,
+      weightUom: payload.weightUom
+    };
+
+    this.http.post<{ issues: string }>(`${webConfig.exposedUrl}/api/ffb-production/generate-issues`, context).subscribe({
+      next: (res) => {
+        this.form.patchValue({ issues: res.issues });
+        this.snackBar.open('Issues generated!', 'Close', { duration: 2000 });
+        this.generatingIssues = false;
+      },
+      error: (err) => {
+        console.error(err);
+        this.snackBar.open('Failed to generate issues.', 'Close', { duration: 3000 });
+        this.generatingIssues = false;
+      }
+    });
+  }
+
+
   randomize() {
     // Randomize needs to pick valid existing sites now
     if (this.sites.length === 0) {
@@ -302,7 +340,8 @@ export class CreateFfbProductionDialogComponent implements OnInit {
       productionDate: new Date(formVal.productionDate).toISOString(),
       site: { id: formVal.site._id || formVal.site.id, name: formVal.site.name },
       phase: { id: formVal.phase._id || formVal.phase.id, name: formVal.phase.name },
-      block: { id: formVal.block._id || formVal.block.id, name: formVal.block.name }
+      block: { id: formVal.block._id || formVal.block.id, name: formVal.block.name },
+      issues: formVal.issues
     };
 
     // If editing, we need the ID? 

+ 1 - 0
src/app/ffb/ffb-production.interface.ts

@@ -10,6 +10,7 @@ export interface FFBProduction {
   quantityUom: string;
   score?: number;
   remarks?: string;
+  issues?: string;
 }
 
 export interface PaginatedResponse<T> {