ソースを参照

changes so far

Dr-Swopt 2 週間 前
コミット
1d54761cc5

+ 0 - 10
src/app/attendance/attendance.component.css

@@ -1,10 +0,0 @@
-:host {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-top: 2rem;
-}
-
-qrcode {
-  margin-top: 1rem;
-}

+ 0 - 58
src/app/attendance/attendance.component.html

@@ -1,58 +0,0 @@
-<h1>{{ isMobile ? 'Scan QR with Camera' : 'Scan to take attendance!' }}</h1>
-
-<div *ngIf="loading">
-  <p>Loading…</p>
-</div>
-
-<!-- Web QR code & attendance list -->
-<div *ngIf="!isMobile && !loading && qrData"
-  style="display: flex; gap: 2rem; align-items: flex-start; flex-wrap: wrap;">
-
-  <!-- QR Code -->
-  <div>
-    <qrcode [qrdata]="qrData" [width]="256" [errorCorrectionLevel]="'M'"></qrcode>
-  </div>
-
-  <!-- Attendance List -->
-  <div style="min-width: 300px; flex: 1;">
-    <h2>Attendance List</h2>
-    <table style="width: 100%; border-collapse: collapse;">
-      <thead>
-        <tr>
-          <th style="text-align: left; padding: 0.5rem; border-bottom: 1px solid #ccc;">Name</th>
-          <th style="text-align: left; padding: 0.5rem; border-bottom: 1px solid #ccc;">Time</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr *ngFor="let attendee of attendanceList">
-          <td style="padding: 0.5rem; border-bottom: 1px solid #eee;">{{ attendee.name }}</td>
-          <td style="padding: 0.5rem; border-bottom: 1px solid #eee;">
-            {{ attendee.time | date: 'medium' }}
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</div>
-
-<div *ngIf="!isMobile && !loading && !qrData">
-  <p>Unable to generate QR code.</p>
-</div>
-
-<!-- Mobile scan section -->
-<div *ngIf="isMobile && !loading">
-  <div *ngIf="scannedResult; else noResult">
-    <p><strong>Scanned Result:</strong></p>
-    <code>{{ scannedResult }}</code>
-  </div>
-  <ng-template #noResult>
-    <p>Tap below to scan a QR code.</p>
-  </ng-template>
-
-  <button (click)="startScan()">Scan</button>
-
-  <div *ngIf="postResponse" style="margin-top: 1rem;">
-    <strong>Server Response:</strong>
-    <p>{{ postResponse }}</p>
-  </div>
-</div>

+ 0 - 23
src/app/attendance/attendance.component.spec.ts

@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { AttendanceComponent } from './attendance.component';
-
-describe('AttendanceComponent', () => {
-  let component: AttendanceComponent;
-  let fixture: ComponentFixture<AttendanceComponent>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [AttendanceComponent]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(AttendanceComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});

+ 0 - 109
src/app/attendance/attendance.component.ts

@@ -1,109 +0,0 @@
-import { Component, inject, OnInit } from '@angular/core';
-import { Capacitor } from '@capacitor/core';
-import { CapacitorBarcodeScanner } from '@capacitor/barcode-scanner';
-import { CommonModule } from '@angular/common';
-import { QRCodeComponent } from 'angularx-qrcode';
-import { AuthService } from '../services/auth.service';
-import { AuthResponse, IncomingMessage } from '../interfaces/interface';
-import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
-import { SocketService } from '../services/socket.service';
-@Component({
-  standalone: true,
-  selector: 'app-attendance',
-  imports: [CommonModule, QRCodeComponent, MatSnackBarModule],
-  templateUrl: './attendance.component.html',
-  styleUrls: ['./attendance.component.css']
-})
-export class AttendanceComponent implements OnInit {
-  private snackbar = inject(MatSnackBar);
-  attendanceList: { name: string, time: Date }[] = []
-  isMobile = false;
-  qrData = '';
-  loading = false;
-  scannedResult = '';
-  postResponse = '';
-
-  constructor(private auth: AuthService, private socket: SocketService) {
-    if (!this.isMobile) {
-      this.socket.onMessage().subscribe((message: IncomingMessage) => {
-        // console.log(message)
-        if (message.action == `Attendance`) this.attendanceList.push({ name: message.name, time: message.time })
-      })
-    }
-  }
-
-  ngOnInit(): void {
-    this.isMobile = Capacitor.getPlatform() !== 'web';
-
-    if (!this.isMobile) {
-      this.loading = true;
-      this.auth.getServerUrl().subscribe({
-        next: (url) => {
-          this.qrData = JSON.stringify({ action: 'Attendance', serverUrl: url });
-          this.loading = false;
-        },
-        error: () => {
-          this.loading = false;
-        }
-      });
-    }
-  }
-
-  async startScan() {
-    this.loading = true;
-
-    try {
-      const result = await CapacitorBarcodeScanner.scanBarcode({ hint: 0 });
-      this.loading = false;
-
-      if (result?.ScanResult) {
-        this.scannedResult = result.ScanResult;
-        console.log('[QR Scan Result]', this.scannedResult);
-
-        let parsed: any;
-        try {
-          parsed = JSON.parse(this.scannedResult);
-        } catch (err) {
-          console.error('Invalid QR format:', err);
-          this.postResponse = 'Invalid QR code format.';
-          return;
-        }
-
-        if (!parsed?.action || !parsed?.serverUrl && parsed.action != `Attendance`) {
-          this.postResponse = 'Missing required data in QR code.';
-          return;
-        }
-
-        // ✅ Show snackbar before sending
-        const snackRef = this.snackbar.open('Submitting attendance...', 'Dismiss', {
-          duration: undefined, // stays until explicitly closed
-        });
-
-        // ✅ POST via AuthService
-        this.loading = true;
-        this.auth.reportAttendance(parsed.serverUrl).subscribe({
-          next: (res: AuthResponse) => {
-            console.log('[Attendance Success]', res);
-            this.postResponse = 'Attendance submitted successfully!';
-            this.snackbar.open('✅ Attendance submitted!', 'Close', { duration: 3000 });
-            snackRef.dismiss(); // close the first snackbar
-            this.loading = false;
-          },
-          error: (err) => {
-            console.error('[Attendance Error]', err);
-            this.postResponse = 'Failed to submit attendance.';
-            this.snackbar.open('❌ Failed to submit attendance.', 'Close', { duration: 3000 });
-            snackRef.dismiss();
-            this.loading = false;
-          }
-        });
-      } else {
-        this.scannedResult = 'No QR code found.';
-      }
-    } catch (error) {
-      console.error('Scan failed', error);
-      this.loading = false;
-      this.scannedResult = 'Scan failed.';
-    }
-  }
-}

+ 0 - 1
src/app/components/activity-dialog/create-activity-dialog.component.ts

@@ -1,5 +1,4 @@
 
-// create-activity-dialog.component.timport { Component, Inject } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import {
   FormBuilder,

+ 0 - 7
src/app/dashboard/dashboard.component.html

@@ -8,13 +8,6 @@
     <app-dashboard-home></app-dashboard-home>
   </mat-tab>
 
-  <!-- <mat-tab label="Attendance">
-    <app-attendance></app-attendance>
-  </mat-tab>
-
-  <mat-tab label="Payment">
-    <app-payment></app-payment>
-  </mat-tab> -->
   <mat-tab label="Activities">
     <app-activity></app-activity>
   </mat-tab>

+ 0 - 5
src/app/dashboard/dashboard.component.ts

@@ -4,9 +4,7 @@ import { CommonModule } from '@angular/common';
 import { MatTabsModule } from '@angular/material/tabs';
 import { MatToolbarModule } from '@angular/material/toolbar';
 import { MatButtonModule } from '@angular/material/button';
-import { AttendanceComponent } from '../attendance/attendance.component';
 import { DashboardHomeComponent } from './dashboard.home.component';
-import { PaymentComponent } from '../payment/payment.component';
 import { PlantationTreeComponent } from "../plantation/plantation-tree.component";
 import { ActivityComponent } from '../activity/activity.component';
 import { FfbHarvestComponent } from "../ffb/ffb-harvest.component";
@@ -20,8 +18,6 @@ import { FfbHarvestComponent } from "../ffb/ffb-harvest.component";
     MatToolbarModule,
     MatButtonModule,
     DashboardHomeComponent,
-    PaymentComponent,
-    AttendanceComponent,
     PlantationTreeComponent,
     ActivityComponent,
     FfbHarvestComponent
@@ -32,7 +28,6 @@ import { FfbHarvestComponent } from "../ffb/ffb-harvest.component";
 export class DashboardComponent {
 
   constructor(private auth: AuthService) {
-
   }
 
   logout(): void {

+ 55 - 34
src/app/ffb/ffb-harvest.component.css

@@ -1,55 +1,76 @@
-:host {
-  display: block;
-  padding: 12px;
+.clickable-row {
+  cursor: pointer;
+  transition: background-color 0.2s;
+}
+
+.clickable-row:hover {
+  background-color: #f5f5f5;
 }
 
-.controls {
+.toolbar {
   display: flex;
-  gap: 12px;
-  align-items: center;
-  margin-bottom: 10px;
+  justify-content: space-between;
+  align-items: flex-start;
+  flex-wrap: wrap;
+  gap: 0.5rem;
 }
 
-.small {
-  width: 220px;
+.left-section {
+  display: flex;
+  align-items: flex-end;
+  flex-wrap: wrap;
+  gap: 1rem;
+  margin-top: 0.5rem;
 }
 
-.table-wrap {
-  overflow: auto;
-  max-height: 360px;
-  margin-bottom: 12px;
+.left-section mat-form-field {
+  margin-bottom: 0 !important;
+  transform: translateY(4px);
 }
 
-.table-wrap table {
-  width: 100%;
+.search-field {
+  width: 250px;
+  max-width: 400px;
+  margin-top: 0 !important;
 }
 
-.chart {
-  margin: 12px 0;
-  height: 260px;
+.left-section button {
+  margin-top: 0 !important;
+  height: 56px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
 }
 
-.form-area {
-  margin-top: 16px;
-  padding: 12px;
-  border: 1px solid rgba(0,0,0,0.08);
-  border-radius: 8px;
+button[color="warn"] {
+  color: white !important;
+  background-color: #d32f2f !important;
 }
 
-.row {
-  display: flex;
-  gap: 12px;
-  flex-wrap: wrap;
-  margin-bottom: 12px;
+button[color="primary"] {
+  margin-bottom: 10px;
 }
 
-.row mat-form-field {
-  flex: 1 1 220px;
+.left-section h2 {
+  margin: 0;
+  line-height: 56px;
 }
 
-.form-actions {
-  display: flex;
-  gap: 12px;
-  justify-content: flex-end;
+table {
+  width: 100%;
   margin-top: 10px;
 }
+
+mat-progress-spinner {
+  display: block;
+  margin: 2rem auto;
+}
+
+.spin {
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  from { transform: rotate(0deg); }
+  to { transform: rotate(360deg); }
+}

+ 16 - 13
src/app/ffb/ffb-harvest.component.html

@@ -39,12 +39,12 @@
 
         <ng-container matColumnDef="weight">
           <th mat-header-cell *matHeaderCellDef>Weight</th>
-          <td mat-cell *matCellDef="let el">{{ el.weight }} {{ el.weightUOM }}</td>
+          <td mat-cell *matCellDef="let el">{{ el.weight }} {{ el.weightUom }}</td>
         </ng-container>
 
         <ng-container matColumnDef="quantity">
           <th mat-header-cell *matHeaderCellDef>Qty</th>
-          <td mat-cell *matCellDef="let el">{{ el.quantity }} {{ el.quantityUOM }}</td>
+          <td mat-cell *matCellDef="let el">{{ el.quantity }} {{ el.quantityUom }}</td>
         </ng-container>
 
         <ng-container matColumnDef="actions">
@@ -119,13 +119,14 @@
             <mat-label>Weight</mat-label>
             <input matInput type="number" formControlName="weight">
           </mat-form-field>
-
           <mat-form-field appearance="outline">
             <mat-label>Weight UOM</mat-label>
-            <mat-select formControlName="weightUOM">
-              <mat-option value="kg">kg</mat-option>
-              <mat-option value="ton">ton</mat-option>
-            </mat-select>
+            <input type="text" matInput [matAutocomplete]="weightUomAuto" formControlName="weightUOM">
+            <mat-autocomplete #weightUomAuto="matAutocomplete">
+              <mat-option *ngFor="let uom of weightUomOptions" [value]="uom">
+                {{ uom }}
+              </mat-option>
+            </mat-autocomplete>
           </mat-form-field>
         </div>
 
@@ -137,11 +138,13 @@
 
           <mat-form-field appearance="outline">
             <mat-label>Quantity UOM</mat-label>
-            <mat-select formControlName="quantityUOM">
-              <mat-option value="bunch">bunch</mat-option>
-              <mat-option value="kg">kg</mat-option>
-            </mat-select>
-          </mat-form-field>
+            <input type="text" matInput [matAutocomplete]="quantityUomAuto" formControlName="quantityUOM">
+            <mat-autocomplete #quantityUomAuto="matAutocomplete">
+              <mat-option *ngFor="let uom of quantityUomOptions" [value]="uom">
+                {{ uom }}
+              </mat-option>
+            </mat-autocomplete>
+            </mat-form-field>
         </div>
 
         <div class="form-actions">
@@ -152,4 +155,4 @@
     </div>
 
   </mat-card-content>
-</mat-card>
+</mat-card>

+ 5 - 1
src/app/ffb/ffb-harvest.component.ts

@@ -10,13 +10,14 @@ import { MatSelectModule } from '@angular/material/select';
 import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
 import { MatCardModule } from '@angular/material/card';
 import { FFBHarvestService } from './ffb-harvest.service';
-import { FFBHarvest } from './ffb-harvest.model';
+import { FFBHarvest } from './ffb-harvest.interface';
 import { NgChartsModule } from 'ng2-charts';
 import { ChartData, ChartOptions } from 'chart.js';
 import { HttpClientModule } from '@angular/common/http';
 import { map } from 'rxjs/operators';
 import { MatDatepickerModule } from '@angular/material/datepicker';
 import { MatNativeDateModule } from '@angular/material/core';
+import { MatAutocompleteModule } from '@angular/material/autocomplete';
 
 @Component({
   selector: 'app-ffb-harvest',
@@ -36,11 +37,14 @@ import { MatNativeDateModule } from '@angular/material/core';
     MatSnackBarModule,
     MatDatepickerModule,
     MatNativeDateModule,
+    MatAutocompleteModule,
     MatCardModule,
     NgChartsModule
   ],
 })
 export class FfbHarvestComponent implements OnInit {
+  weightUomOptions: string[] = ['kg', 'ton'];
+  quantityUomOptions: string[] = ['bunch', 'kg'];
   harvests: FFBHarvest[] = [];
   displayedColumns: string[] = ['harvestDate', 'site', 'phase', 'block', 'weight', 'quantity', 'actions'];
   form: FormGroup;

+ 0 - 0
src/app/ffb/ffb-harvest.model.ts → src/app/ffb/ffb-harvest.interface.ts


+ 1 - 1
src/app/ffb/ffb-harvest.service.ts

@@ -1,8 +1,8 @@
 import { Injectable } from '@angular/core';
 import { HttpClient, HttpParams } from '@angular/common/http';
 import { Observable } from 'rxjs';
-import { FFBHarvest } from './ffb-harvest.model';
 import { webConfig } from '../config';
+import { FFBHarvest } from './ffb-harvest.interface';
 
 @Injectable({
   providedIn: 'root'

+ 0 - 10
src/app/payment/payment.component.css

@@ -1,10 +0,0 @@
-:host {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-top: 2rem;
-}
-
-qrcode {
-  margin-top: 1rem;
-}

+ 0 - 60
src/app/payment/payment.component.html

@@ -1,60 +0,0 @@
-<h1>{{ isMobile ? 'Scan QR & Authenticate to Pay' : 'Scan to make Payment!' }}</h1>
-
-<!-- Loading State -->
-<div *ngIf="loading">
-  <p>Loading…</p>
-</div>
-
-<!-- Web QR Code + Payment List -->
-<div *ngIf="!isMobile && !loading && qrData"
-     style="display: flex; gap: 2rem; align-items: flex-start; flex-wrap: wrap;">
-
-  <!-- QR Code -->
-  <div>
-    <qrcode [qrdata]="qrData" [width]="256" [errorCorrectionLevel]="'M'"></qrcode>
-  </div>
-
-  <!-- Payment List -->
-  <div style="min-width: 300px; flex: 1;">
-    <h2>Payment List</h2>
-    <table style="width: 100%; border-collapse: collapse;">
-      <thead>
-        <tr>
-          <th style="text-align: left; padding: 0.5rem; border-bottom: 1px solid #ccc;">Name</th>
-          <th style="text-align: left; padding: 0.5rem; border-bottom: 1px solid #ccc;">Time</th>
-        </tr>
-      </thead>
-      <tbody>
-        <tr *ngFor="let payee of paymentList">
-          <td style="padding: 0.5rem; border-bottom: 1px solid #eee;">{{ payee.name }}</td>
-          <td style="padding: 0.5rem; border-bottom: 1px solid #eee;">
-            {{ payee.time | date: 'medium' }}
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</div>
-
-<!-- If unable to generate QR -->
-<div *ngIf="!isMobile && !loading && !qrData">
-  <p>Unable to generate QR code.</p>
-</div>
-
-<!-- Mobile Section -->
-<div *ngIf="isMobile && !loading">
-  <div *ngIf="scannedResult; else noResult">
-    <p><strong>Scanned Result:</strong></p>
-    <code>{{ scannedResult }}</code>
-  </div>
-  <ng-template #noResult>
-    <p>Tap below to scan a QR code and authenticate payment.</p>
-  </ng-template>
-
-  <button (click)="startScan()">📷 Scan & Pay</button>
-
-  <div *ngIf="postResponse" style="margin-top: 1rem;">
-    <strong>Server Response:</strong>
-    <p>{{ postResponse }}</p>
-  </div>
-</div>

+ 0 - 23
src/app/payment/payment.component.spec.ts

@@ -1,23 +0,0 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
-
-import { PaymentComponent } from './payment.component';
-
-describe('PaymentComponent', () => {
-  let component: PaymentComponent;
-  let fixture: ComponentFixture<PaymentComponent>;
-
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [PaymentComponent]
-    })
-    .compileComponents();
-
-    fixture = TestBed.createComponent(PaymentComponent);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
-
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});

+ 0 - 133
src/app/payment/payment.component.ts

@@ -1,133 +0,0 @@
-import { Component, inject, OnInit } from '@angular/core';
-import { Capacitor } from '@capacitor/core';
-import { CapacitorBarcodeScanner } from '@capacitor/barcode-scanner';
-import { CommonModule } from '@angular/common';
-import { QRCodeComponent } from 'angularx-qrcode';
-import { AuthService } from '../services/auth.service';
-import { AuthResponse, IncomingMessage } from '../interfaces/interface';
-import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar';
-import { SocketService } from '../services/socket.service';
-import { NativeBiometric } from '@capgo/capacitor-native-biometric'; // ✅ import
-
-@Component({
-  standalone: true,
-  selector: 'app-payment',
-  imports: [CommonModule, QRCodeComponent, MatSnackBarModule],
-  templateUrl: './payment.component.html',
-  styleUrls: ['./payment.component.css']
-})
-export class PaymentComponent implements OnInit {
-  private snackbar = inject(MatSnackBar);
-  paymentList: { name: string, time: Date }[] = [];
-  isMobile = false;
-  qrData = '';
-  loading = false;
-  scannedResult = '';
-  postResponse = '';
-
-  constructor(private auth: AuthService, private socket: SocketService) {
-    if (!this.isMobile) {
-      this.socket.onMessage().subscribe((message: IncomingMessage) => {
-        if (message.action === 'Payment') {
-          this.paymentList.push({ name: message.name, time: message.time });
-        }
-      });
-    }
-  }
-
-  ngOnInit(): void {
-    this.isMobile = Capacitor.getPlatform() !== 'web';
-
-    if (!this.isMobile) {
-      this.loading = true;
-      this.auth.getServerUrl().subscribe({
-        next: (url) => {
-          this.qrData = JSON.stringify({ action: 'Payment', serverUrl: url });
-          this.loading = false;
-        },
-        error: () => {
-          this.loading = false;
-        }
-      });
-    }
-  }
-
-  async startScan() {
-    this.loading = true;
-
-    try {
-      const result = await CapacitorBarcodeScanner.scanBarcode({ hint: 0 });
-      this.loading = false;
-
-      if (!result?.ScanResult) {
-        this.scannedResult = 'No QR code found.';
-        return;
-      }
-
-      this.scannedResult = result.ScanResult;
-      console.log('[QR Scan Result]', this.scannedResult);
-
-      let parsed: any;
-      try {
-        parsed = JSON.parse(this.scannedResult);
-      } catch (err) {
-        console.error('Invalid QR format:', err);
-        this.postResponse = 'Invalid QR code format.';
-        return;
-      }
-
-      if (!parsed?.action || !parsed?.serverUrl || parsed.action !== 'Payment') {
-        this.postResponse = 'Missing or incorrect data in QR code.';
-        return;
-      }
-
-      // ✅ Biometric check
-      const available = await NativeBiometric.isAvailable();
-      if (!available.isAvailable) {
-        this.snackbar.open('Biometrics not available.', 'Close', { duration: 3000 });
-        return;
-      }
-
-      const verified = await NativeBiometric.verifyIdentity({
-        reason: 'Scan biometrics to confirm payment',
-        title: 'Biometric Authentication',
-        subtitle: 'Required for security',
-        description: 'Use fingerprint or face to proceed'
-      }).then(() => true).catch((err) => {
-        console.warn('Biometric auth failed or cancelled', err);
-        this.snackbar.open('Authentication cancelled.', 'Close', { duration: 3000 });
-        return false;
-      });
-
-      if (!verified) return;
-
-      // ✅ Proceed to POST
-      const snackRef = this.snackbar.open('Submitting payment...', 'Dismiss', {
-        duration: undefined,
-      });
-
-      this.loading = true;
-      this.auth.reportPayment(parsed.serverUrl, verified).subscribe({
-        next: (res: AuthResponse) => {
-          console.log('[Payment Success]', res);
-          this.postResponse = 'Payment submitted successfully!';
-          this.snackbar.open('✅ Payment submitted!', 'Close', { duration: 3000 });
-          snackRef.dismiss();
-          this.loading = false;
-        },
-        error: (err) => {
-          console.error('[Payment Error]', err);
-          this.postResponse = 'Failed to submit payment.';
-          this.snackbar.open('❌ Failed to submit payment.', 'Close', { duration: 3000 });
-          snackRef.dismiss();
-          this.loading = false;
-        }
-      });
-
-    } catch (error) {
-      console.error('Scan failed', error);
-      this.loading = false;
-      this.scannedResult = 'Scan failed.';
-    }
-  }
-}