Dr-Swopt 1 tydzień temu
rodzic
commit
e1a786de10

BIN
ai-data-entry-ui/build_log.txt


+ 1 - 16
ai-data-entry-ui/src/app/app.css

@@ -1,16 +1 @@
-header {
-    background: #007bff;
-    color: white;
-    padding: 20px;
-    text-align: center;
-    margin-bottom: 30px;
-}
-
-header h1 {
-    margin: 0;
-    font-size: 1.8rem;
-}
-
-main {
-    padding: 0 20px;
-}
+/* Global styles should be placed in styles.css. Component-specific styles are in their respective .css files. */

+ 16 - 8
ai-data-entry-ui/src/app/app.routes.ts

@@ -1,13 +1,21 @@
 import { Routes } from '@angular/router';
-import { UserSelectionComponent } from './components/user-selection/user-selection.component';
-import { DashboardComponent } from './components/claims-dashboard/claims-dashboard.component';
-import { ClaimFormComponent } from './components/claim-form/claim-form.component';
-import { UserManagementComponent } from './components/user-management/user-management.component';
 
 export const routes: Routes = [
-  { path: '', component: UserSelectionComponent },
-  { path: 'dashboard', component: DashboardComponent },
-  { path: 'extract', component: ClaimFormComponent },
-  { path: 'user-management', component: UserManagementComponent },
+  { 
+    path: '', 
+    loadComponent: () => import('./components/claims-dashboard/claims-dashboard.component').then(m => m.DashboardComponent) 
+  },
+  { 
+    path: 'user-selection', 
+    loadComponent: () => import('./components/user-selection/user-selection.component').then(m => m.UserSelectionComponent) 
+  },
+  { 
+    path: 'new-claim', 
+    loadComponent: () => import('./components/claim-form/claim-form.component').then(m => m.ClaimFormComponent) 
+  },
+  { 
+    path: 'user-management', 
+    loadComponent: () => import('./components/user-management/user-management.component').then(m => m.UserManagementComponent) 
+  },
   { path: '**', redirectTo: '' }
 ];

+ 1 - 2
ai-data-entry-ui/src/app/app.ts

@@ -1,11 +1,10 @@
 import { Component } from '@angular/core';
 import { RouterOutlet } from '@angular/router';
-import { ClaimFormComponent } from './components/claim-form/claim-form.component';
 
 @Component({
   selector: 'app-root',
   standalone: true,
-  imports: [RouterOutlet, ClaimFormComponent],
+  imports: [RouterOutlet],
   templateUrl: './app.html',
   styleUrl: './app.css'
 })

+ 110 - 63
ai-data-entry-ui/src/app/components/claim-form/claim-form.component.css

@@ -2,71 +2,64 @@
     display: grid;
     grid-template-columns: 1fr 1fr;
     height: 100vh;
-    background-color: #0c0c14;
-    color: #fff;
+    background-color: var(--bg-primary);
+    color: var(--text-primary);
     font-family: 'Inter', sans-serif;
 }
 
 .receipt-container {
     padding: 2rem;
-    border-right: 1px solid rgba(255, 255, 255, 0.05);
-    overflow-y: auto;
-    background: #12121e;
-}
-
-.form-container {
-    padding: 2.5rem;
+    border-right: 1px solid var(--border-color);
     overflow-y: auto;
+    background: #f8fafc;
 }
 
-.hero-amount {
-    font-size: 3.5rem;
-    font-weight: 800;
-    margin: 0.5rem 0;
-    background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
-    -webkit-background-clip: text;
-    background-clip: text;
-    -webkit-text-fill-color: transparent;
+.form-header {
+    margin-bottom: 2rem;
 }
 
-.spent-hero {
-    margin-bottom: 2rem;
-    padding-bottom: 2rem;
-    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+.financial-row {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 1.5rem;
+    margin-bottom: 1rem;
 }
 
-.hero-label {
-    text-transform: uppercase;
-    font-size: 0.75rem;
-    letter-spacing: 0.1em;
-    color: #a0a0c0;
+.read-only-box {
+    padding: 0.75rem;
+    background: #f1f5f9;
+    border: 1px solid var(--border-color);
+    border-radius: 8px;
+    font-weight: 700;
+    color: var(--accent-primary);
+    font-size: 1.1rem;
 }
 
-.hero-hint {
-    font-size: 0.85rem;
-    color: #666;
+.form-actions {
+    margin-top: 1rem;
 }
 
 .deduction-warning {
-    background: rgba(255, 77, 79, 0.1);
-    border: 1px solid #ff4d4f;
+    background: #fef9c3;
+    border: 1px solid #facc15;
     border-radius: 12px;
     padding: 1.25rem;
     display: flex;
     gap: 1rem;
     margin-bottom: 2rem;
+    box-shadow: var(--shadow-sm);
 }
 
 .deduction-warning h3 {
     margin: 0;
     font-size: 1rem;
-    color: #ff4d4f;
+    color: #854d0e;
 }
 
 .deduction-warning p {
     margin: 5px 0 0;
     font-size: 0.9rem;
-    color: #ff7875;
+    color: #a16207;
 }
 
 .form-field {
@@ -76,23 +69,26 @@
 .form-field label {
     display: block;
     margin-bottom: 0.5rem;
-    color: #a0a0c0;
+    color: var(--text-secondary);
     font-size: 0.9rem;
+    font-weight: 500;
 }
 
 .form-field input {
     width: 100%;
     padding: 0.75rem;
-    background: rgba(255, 255, 255, 0.03);
-    border: 1px solid rgba(255, 255, 255, 0.1);
+    background: var(--bg-primary);
+    border: 1px solid var(--border-color);
     border-radius: 8px;
-    color: #fff;
+    color: var(--text-primary);
     font-size: 1rem;
+    transition: border-color 0.2s;
 }
 
 .form-field input:focus {
-    border-color: #4facfe;
+    border-color: var(--accent-primary);
     outline: none;
+    background: #fff;
 }
 
 .submit-btn {
@@ -100,10 +96,16 @@
     padding: 1rem;
     border-radius: 8px;
     border: none;
-    background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
+    background: linear-gradient(90deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
     color: #fff;
     font-weight: 700;
     cursor: pointer;
+    box-shadow: var(--shadow-sm);
+    transition: transform 0.2s;
+}
+
+.submit-btn:hover {
+    transform: translateY(-1px);
 }
 
 .transparency-section {
@@ -111,9 +113,9 @@
 }
 
 .debug-toggle {
-    background: transparent;
-    border: 1px solid rgba(255, 255, 255, 0.1);
-    color: #a0a0c0;
+    background: var(--bg-primary);
+    border: 1px solid var(--border-color);
+    color: var(--text-secondary);
     padding: 0.5rem 1rem;
     border-radius: 6px;
     cursor: pointer;
@@ -124,31 +126,33 @@
 
 .raw-json-view {
     margin-top: 1.5rem;
-    background: #000;
+    background: #f1f5f9;
     padding: 1.5rem;
     border-radius: 12px;
     font-size: 0.8rem;
+    color: var(--text-primary);
+    border: 1px solid var(--border-color);
 }
 
 .ai-reasoning-box {
-    border-bottom: 1px solid #333;
+    border-bottom: 1px solid var(--border-color);
     padding-bottom: 1rem;
     margin-bottom: 1rem;
 }
 
 .ai-reasoning-box h4 {
     margin: 0 0 0.5rem;
-    color: #4facfe;
+    color: var(--accent-primary);
 }
 
 .ai-reasoning-box p {
-    color: #fff;
+    color: var(--text-secondary);
     font-style: italic;
     line-height: 1.5;
 }
 
 .raw-json-view pre {
-    color: #00e676;
+    color: #059669;
     margin: 0;
     overflow-x: auto;
 }
@@ -156,8 +160,8 @@
 .preview-wrapper {
     width: 100%;
     min-height: 400px;
-    background: rgba(255, 255, 255, 0.02);
-    border: 2px dashed rgba(255, 255, 255, 0.1);
+    background: var(--bg-card);
+    border: 2px dashed var(--border-color);
     border-radius: 16px;
     display: flex;
     justify-content: center;
@@ -169,10 +173,12 @@
 .receipt-preview {
     max-width: 100%;
     max-height: 600px;
+    border-radius: 8px;
+    box-shadow: var(--shadow-sm);
 }
 
 .upload-trigger {
-    background: #4facfe;
+    background: var(--accent-primary);
     color: #fff;
     border: none;
     padding: 0.75rem 1.5rem;
@@ -185,35 +191,40 @@
 .user-selector-field select {
     width: 100%;
     padding: 1rem;
-    background: #1a1a2e;
-    color: #fff;
-    border: 1px solid #4facfe;
+    background: var(--bg-card);
+    color: var(--text-primary);
+    border: 1px solid var(--border-color);
     border-radius: 12px;
 }
 
 .back-btn {
     background: transparent;
     border: none;
-    color: #a0a0c0;
+    color: var(--text-secondary);
     cursor: pointer;
     margin-bottom: 1rem;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    font-weight: 500;
 }
 
 .active-user-badge {
     display: flex;
     align-items: center;
     gap: 15px;
-    background: rgba(79, 172, 254, 0.1);
+    background: rgba(59, 130, 246, 0.1);
     padding: 15px;
     border-radius: 12px;
-    border: 1px solid rgba(79, 172, 254, 0.3);
+    border: 1px solid rgba(59, 130, 246, 0.2);
     margin-top: 15px;
 }
 
 .active-user-badge .avatar {
     width: 40px;
     height: 40px;
-    background: #4facfe;
+    background: var(--accent-primary);
+    color: #fff;
     border-radius: 50%;
     display: flex;
     align-items: center;
@@ -227,22 +238,23 @@
     flex: 1;
 }
 
-.active-user-badge .name { font-weight: 600; font-size: 1rem; }
-.active-user-badge .allowance { font-size: 0.8rem; color: #4facfe; }
+.active-user-badge .name { font-weight: 600; font-size: 1rem; color: var(--text-primary); }
+.active-user-badge .allowance { font-size: 0.8rem; color: var(--accent-primary); font-weight: 500; }
 
 .change-user {
-    background: transparent;
-    border: 1px solid #4facfe;
-    color: #4facfe;
+    background: #fff;
+    border: 1px solid var(--accent-primary);
+    color: var(--accent-primary);
     padding: 4px 12px;
     border-radius: 6px;
     cursor: pointer;
     font-size: 0.8rem;
+    font-weight: 600;
 }
 
 .caps-banner {
     margin-top: 10px !important;
-    background: #ff4d4f;
+    background: #ef4444;
     color: #fff !important;
     display: inline-block;
     padding: 2px 8px;
@@ -251,3 +263,38 @@
     font-weight: bold;
     text-transform: uppercase;
 }
+
+.ai-assistant-panel {
+    margin-top: 1.5rem;
+    padding-top: 1.5rem;
+    border-top: 1px solid var(--border-color);
+    text-align: center;
+}
+
+.action-btn {
+    background: linear-gradient(135deg, #a855f7 0%, #d946ef 100%);
+    color: #fff;
+    border: none;
+    padding: 1rem 1.5rem;
+    border-radius: 8px;
+    font-weight: 700;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    transition: transform 0.2s, opacity 0.2s;
+}
+
+.action-btn:hover:not(:disabled) {
+    transform: translateY(-2px);
+}
+
+.action-btn:disabled {
+    opacity: 0.7;
+    cursor: not-allowed;
+}
+
+.w-full {
+    width: 100%;
+}

+ 53 - 40
ai-data-entry-ui/src/app/components/claim-form/claim-form.component.html

@@ -1,14 +1,14 @@
 <div class="split-screen-container">
-  <!-- Left Column: Receipt Preview -->
+  <!-- Left Column: Receipt Preview / Optional AI -->
   <div class="receipt-container">
     <div class="back-nav">
-      <button class="back-btn" routerLink="/dashboard">
+      <button class="back-btn" routerLink="/">
         <lucide-icon [name]="ArrowLeft" class="icon-small"></lucide-icon> Exit to Dashboard
       </button>
     </div>
 
     <header class="section-header" *ngIf="currentUser">
-      <h2>Step 1: Active Employee</h2>
+      <h2>Step 1: Reference Material (Optional)</h2>
       <div class="active-user-badge">
          <div class="avatar">{{ currentUser.name[0] }}</div>
          <div class="details">
@@ -20,7 +20,6 @@
     </header>
 
     <div class="receipt-viewer" *ngIf="currentUser">
-      <h2>Step 2: Upload Receipt</h2>
       <div class="preview-wrapper" 
            [class.dragging]="isDragging"
            (dragover)="onDragOver($event)" 
@@ -42,67 +41,81 @@
           </div>
         </div>
       </div>
+
+      <!-- Optional AI Assistant Trigger -->
+      <div class="ai-assistant-panel" *ngIf="selectedFile">
+         <button class="action-btn w-full" (click)="autoFillWithAI()" [disabled]="isLoading">
+           <lucide-icon [name]="Wand2" class="icon-small"></lucide-icon> 
+           {{ isLoading ? 'Extracting details...' : 'Auto-fill form with AI' }}
+         </button>
+         <p class="helper-text" *ngIf="errorMessage" style="color: #ef4444; margin-top: 0.5rem">{{ errorMessage }}</p>
+      </div>
     </div>
   </div>
 
   <!-- Right Column: Data Entry Form -->
-  <div class="form-container" *ngIf="currentUser && extractionResponse">
-    <!-- HERO HEADER: Amount Spent -->
-    <div class="spent-hero">
-       <span class="hero-label">Detected Amount Spent</span>
-       <h1 class="hero-amount">RM {{ extractionResponse.total_amount | number:'1.2-2' }}</h1>
-       <p class="hero-hint">Calculated by AI extraction engine</p>
-    </div>
+  <div class="form-container" *ngIf="currentUser">
+    <header class="form-header">
+       <h1>Step 2: Enter Claim Details</h1>
+       <p>Manually enter your receipt details, or use the AI Assistant to pre-fill the form.</p>
+    </header>
 
     <!-- SPECIFIC DEDUCTION WARNING -->
-    <div class="deduction-warning" *ngIf="claimForm.get('amount_claimed')?.value < extractionResponse.total_amount">
+    <div class="deduction-warning" *ngIf="claimForm.get('amount_spent')?.value > currentUser.medical_allowance">
        <lucide-icon [name]="AlertCircle" class="icon-medium"></lucide-icon>
        <div class="warning-content">
-          <h3>Allowance Limit Exceeded</h3>
-          <p>Warning: RM {{ (extractionResponse.total_amount - claimForm.get('amount_claimed')?.value) | number:'1.2-2' }} deducted due to RM {{ currentUser.medical_allowance | number:'1.2-2' }} annual limit.</p>
-          <p class="caps-banner">Policy Capping Applied</p>
+          <h3>Policy Capping Applied</h3>
+          <p>This claim (RM {{ claimForm.get('amount_spent')?.value | number:'1.2-2' }}) exceeds your remaining RM {{ currentUser.medical_allowance | number:'1.2-2' }} allowance. RM {{ (claimForm.get('amount_spent')?.value - currentUser.medical_allowance) | number:'1.2-2' }} will be deducted from payout.</p>
        </div>
     </div>
 
     <form [formGroup]="claimForm" (ngSubmit)="onSubmit()">
       <div class="form-section">
-        <h3>Claim Details</h3>
-        <div class="form-field">
+        <div class="financial-row">
+          <div class="form-field">
+            <label>Amount Spent (MYR)</label>
+            <input type="number" formControlName="amount_spent" step="0.01" placeholder="E.g. 150.00">
+          </div>
+          <div class="form-field">
+            <label>Claimable Amount (MYR)</label>
+            <div class="read-only-box" [class.capped-text]="claimForm.get('amount_spent')?.value > currentUser.medical_allowance">
+              RM {{ claimForm.get('amount_claimed')?.value | number:'1.2-2' }}
+            </div>
+          </div>
+        </div>
+
+        <div class="form-field" style="margin-top: 1rem;">
           <label>Provider Name</label>
-          <input type="text" formControlName="provider_name">
+          <input type="text" formControlName="provider_name" placeholder="E.g. Klinik Kesihatan">
         </div>
         <div class="form-field">
           <label>Visit Date</label>
           <input type="date" formControlName="visit_date">
         </div>
-        <div class="form-field">
-          <label>Claimable Amount (MYR)</label>
-          <input type="number" formControlName="amount_claimed" step="0.01">
-        </div>
-      </div>
-
-      <!-- AI TRANSPARENCY -->
-      <div class="transparency-section">
-         <button type="button" class="debug-toggle" (click)="showRawJson = !showRawJson">
-           <lucide-icon [name]="showRawJson ? EyeOff : Eye" class="icon-small"></lucide-icon>
-           {{ showRawJson ? 'Hide' : 'View' }} AI Interpretation
-         </button>
-         
-         <div *ngIf="showRawJson" class="raw-json-view">
-           <div class="ai-reasoning-box" *ngIf="extractionResponse.ai_reasoning">
-             <h4>AI Reasoning</h4>
-             <p>{{ extractionResponse.ai_reasoning }}</p>
-           </div>
-           <h4>Full JSON Payload</h4>
-           <pre><code>{{ rawJson }}</code></pre>
-         </div>
       </div>
 
       <div class="form-actions">
         <button type="submit" [disabled]="!claimForm.valid || isLoading || isSubmitting" class="submit-btn" [class.loading]="isLoading || isSubmitting">
-           {{ isSubmitting ? 'Finalizing...' : 'Submit Official Claim' }}
+           {{ isSubmitting ? 'Finalizing Claim...' : 'Submit Official Claim' }}
         </button>
       </div>
     </form>
+
+    <!-- AI TRANSPARENCY -->
+    <div class="transparency-section" *ngIf="extractionResponse">
+       <button type="button" class="debug-toggle" (click)="showRawJson = !showRawJson">
+         <lucide-icon [name]="showRawJson ? EyeOff : Eye" class="icon-small"></lucide-icon>
+         {{ showRawJson ? 'Hide' : 'View' }} AI Interpretation
+       </button>
+       
+       <div *ngIf="showRawJson" class="raw-json-view">
+         <div class="ai-reasoning-box" *ngIf="extractionResponse.ai_reasoning">
+           <h4>AI Reasoning</h4>
+           <p>{{ extractionResponse.ai_reasoning }}</p>
+         </div>
+         <h4>Raw Data Extraction</h4>
+         <pre><code>{{ rawJson }}</code></pre>
+       </div>
+    </div>
   </div>
 </div>

+ 44 - 21
ai-data-entry-ui/src/app/components/claim-form/claim-form.component.ts

@@ -1,11 +1,12 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnInit, OnDestroy } from '@angular/core';
 import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
 import { CommonModule } from '@angular/common';
 import { Router, RouterModule } from '@angular/router';
 import { ExtractionService } from '../../services/extraction.service';
 import { SessionService, User } from '../../services/session.service';
-import { ExtractionResponse } from '../../services/extraction';
-import { LucideAngularModule, ShieldCheck, AlertCircle, CheckCircle, ArrowLeft, Eye, EyeOff } from 'lucide-angular';
+import { ExtractionResponse, ClaimSubmission } from '../../services/extraction';
+import { LucideAngularModule, ShieldCheck, AlertCircle, CheckCircle, ArrowLeft, Eye, EyeOff, Wand2 } from 'lucide-angular';
+import { Subscription } from 'rxjs';
 
 @Component({
   selector: 'app-claim-form',
@@ -14,22 +15,26 @@ import { LucideAngularModule, ShieldCheck, AlertCircle, CheckCircle, ArrowLeft,
   templateUrl: './claim-form.component.html',
   styleUrls: ['./claim-form.component.css']
 })
-export class ClaimFormComponent implements OnInit {
+export class ClaimFormComponent implements OnInit, OnDestroy {
   claimForm: FormGroup;
   currentUser: User | null = null;
   previewUrl: string | null = null;
+  selectedFile: File | null = null;
   extractionResponse: ExtractionResponse | null = null;
   isLoading = false;
   isSubmitting = false;
   isDragging = false;
   showRawJson = false;
 
+  private amountSpentSub: Subscription | null = null;
+
   readonly ShieldCheck = ShieldCheck;
   readonly AlertCircle = AlertCircle;
   readonly CheckCircle = CheckCircle;
   readonly ArrowLeft = ArrowLeft;
   readonly Eye = Eye;
   readonly EyeOff = EyeOff;
+  readonly Wand2 = Wand2;
   errorMessage: string | null = null;
 
   constructor(
@@ -41,8 +46,8 @@ export class ClaimFormComponent implements OnInit {
     this.claimForm = this.fb.group({
       provider_name: ['', Validators.required],
       visit_date: ['', Validators.required],
-      amount_spent: [{ value: 0, disabled: true }, [Validators.required, Validators.min(0.01)]],
-      amount_claimed: [{ value: 0, disabled: false }, [Validators.required, Validators.min(0)]],
+      amount_spent: ['', [Validators.required, Validators.min(0.01)]],
+      amount_claimed: [{ value: 0, disabled: true }],
       currency: ['MYR']
     });
   }
@@ -51,17 +56,28 @@ export class ClaimFormComponent implements OnInit {
     this.currentUser = this.sessionService.getCurrentUser();
     if (!this.currentUser) {
       this.router.navigate(['/']);
+      return;
+    }
+
+    this.amountSpentSub = this.claimForm.get('amount_spent')?.valueChanges.subscribe(() => {
+      this.calculateClaimable();
+    }) || null;
+  }
+
+  ngOnDestroy(): void {
+    if (this.amountSpentSub) {
+      this.amountSpentSub.unsubscribe();
     }
   }
 
   calculateClaimable(): void {
-    if (!this.currentUser || !this.extractionResponse) return;
+    if (!this.currentUser) return;
     
-    const spent = this.claimForm.get('amount_spent')?.value || 0;
+    const spent = Number(this.claimForm.get('amount_spent')?.value) || 0;
     const remaining = this.currentUser.medical_allowance;
     const claimable = Math.min(spent, remaining);
     
-    this.claimForm.patchValue({ amount_claimed: claimable });
+    this.claimForm.patchValue({ amount_claimed: claimable }, { emitEvent: false });
   }
 
   onDragOver(event: DragEvent): void {
@@ -83,25 +99,32 @@ export class ClaimFormComponent implements OnInit {
     
     const files = event.dataTransfer?.files;
     if (files && files.length > 0) {
-      this.processFile(files[0]);
+      this.handleFileSelection(files[0]);
     }
   }
 
   onFileSelected(event: any): void {
     const file: File = event.target.files[0];
     if (file) {
-      this.processFile(file);
+      this.handleFileSelection(file);
     }
   }
 
-  private processFile(file: File): void {
+  private handleFileSelection(file: File): void {
     if (!this.currentUser) return;
-
+    this.selectedFile = file;
     this.previewUrl = URL.createObjectURL(file);
+    this.extractionResponse = null; // reset if existing
+    this.errorMessage = null;
+  }
+
+  autoFillWithAI(): void {
+    if (!this.currentUser || !this.selectedFile) return;
+
     this.isLoading = true;
     this.errorMessage = null;
     
-    this.extractionService.extractData(file, this.currentUser.name, this.currentUser.department).subscribe({
+    this.extractionService.extractData(this.selectedFile, this.currentUser.name, this.currentUser.department).subscribe({
       next: (response) => {
         this.extractionResponse = response;
         this.claimForm.patchValue({
@@ -110,7 +133,7 @@ export class ClaimFormComponent implements OnInit {
           amount_spent: response.total_amount,
           currency: response.currency || 'MYR'
         });
-        this.calculateClaimable();
+        // The valueChanges subscription will trigger calculateClaimable automatically
         this.isLoading = false;
       },
       error: (err: Error) => {
@@ -125,20 +148,20 @@ export class ClaimFormComponent implements OnInit {
     if (this.claimForm.invalid || !this.currentUser) return;
 
     this.isSubmitting = true;
-    const formData = this.claimForm.getRawValue(); // To get disabled fields
+    const formData = this.claimForm.getRawValue();
     
-    const submissionData: ExtractionResponse = {
-      ...this.extractionResponse!,
+    const submissionData: ClaimSubmission = {
       provider_name: formData.provider_name,
       visit_date: formData.visit_date,
-      total_amount: formData.amount_spent,
-      currency: formData.currency
+      amount_spent: Number(formData.amount_spent),
+      currency: formData.currency,
+      extraction_data: this.extractionResponse
     };
 
     this.extractionService.submitClaim(submissionData, this.currentUser.id).subscribe({
       next: () => {
         this.isSubmitting = false;
-        this.router.navigate(['/dashboard']);
+        this.router.navigate(['/']);
       },
       error: (err) => {
         console.error('Submission failed', err);

+ 123 - 49
ai-data-entry-ui/src/app/components/claims-dashboard/claims-dashboard.component.css

@@ -1,16 +1,16 @@
 .dashboard-wrapper {
     display: flex;
     min-height: 100vh;
-    background-color: #0c0c14;
-    color: #fff;
+    background-color: var(--bg-primary);
+    color: var(--text-primary);
     font-family: 'Inter', sans-serif;
 }
 
 /* Sidebar */
 .sidebar {
     width: 260px;
-    background-color: #12121e;
-    border-right: 1px solid rgba(255, 255, 255, 0.05);
+    background-color: var(--bg-card);
+    border-right: 1px solid var(--border-color);
     padding: 2rem 1.5rem;
     display: flex;
     flex-direction: column;
@@ -26,7 +26,7 @@
 }
 
 .logo-icon {
-    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+    background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
     color: #fff;
     width: 40px;
     height: 40px;
@@ -42,7 +42,7 @@
     align-items: center;
     gap: 12px;
     padding: 0.75rem 1rem;
-    color: #a0a0c0;
+    color: var(--text-secondary);
     text-decoration: none;
     border-radius: 8px;
     margin-bottom: 0.5rem;
@@ -50,14 +50,15 @@
 }
 
 .nav-item:hover, .nav-item.active {
-    background: rgba(79, 172, 254, 0.1);
-    color: #4facfe;
+    background: rgba(59, 130, 246, 0.1);
+    color: var(--accent-primary);
 }
 
 .user-profile {
     margin-top: auto;
     padding: 1rem;
-    background: rgba(255, 255, 255, 0.03);
+    background: var(--bg-primary);
+    border: 1px solid var(--border-color);
     border-radius: 12px;
     display: flex;
     align-items: center;
@@ -67,7 +68,8 @@
 .avatar {
     width: 36px;
     height: 36px;
-    background: #4facfe;
+    background: var(--accent-primary);
+    color: #fff;
     border-radius: 50%;
     display: flex;
     align-items: center;
@@ -81,13 +83,13 @@
     flex: 1;
 }
 
-.name { font-size: 0.85rem; font-weight: 600; }
-.dept { font-size: 0.7rem; color: #a0a0c0; }
+.name { font-size: 0.85rem; font-weight: 600; color: var(--text-primary); }
+.dept { font-size: 0.7rem; color: var(--text-secondary); }
 
 .logout-btn {
     background: transparent;
     border: none;
-    color: #ff4d4f;
+    color: #ef4444;
     cursor: pointer;
     padding: 4px;
 }
@@ -106,11 +108,11 @@
     margin-bottom: 2.5rem;
 }
 
-h1 { font-size: 2rem; margin: 0; }
-.content-header p { color: #a0a0c0; margin: 5px 0 0; }
+h1 { font-size: 2rem; margin: 0; color: var(--text-primary); }
+.content-header p { color: var(--text-secondary); margin: 5px 0 0; }
 
 .action-btn {
-    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+    background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
     color: #fff;
     border: none;
     padding: 0.75rem 1.5rem;
@@ -125,6 +127,31 @@ h1 { font-size: 2rem; margin: 0; }
 
 .action-btn:hover { transform: translateY(-2px); }
 
+.quick-actions {
+    display: flex;
+    gap: 12px;
+}
+
+.secondary-btn {
+    background: var(--bg-primary);
+    color: var(--text-secondary);
+    border: 1px solid var(--border-color);
+    padding: 0.75rem 1.5rem;
+    border-radius: 8px;
+    font-weight: 600;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    transition: all 0.2s;
+}
+
+.secondary-btn:hover {
+    background: var(--bg-card);
+    border-color: var(--accent-primary);
+    color: var(--accent-primary);
+}
+
 .stats-overview {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
@@ -133,21 +160,23 @@ h1 { font-size: 2rem; margin: 0; }
 }
 
 .stat-card {
-    background: rgba(255, 255, 255, 0.05);
+    background: var(--bg-card);
     padding: 1.5rem;
     border-radius: 16px;
-    border: 1px solid rgba(255, 255, 255, 0.05);
+    border: 1px solid var(--border-color);
+    box-shadow: var(--shadow-sm);
 }
 
-.stat-card .label { color: #a0a0c0; font-size: 0.85rem; }
-.stat-card .value { display: block; font-size: 1.75rem; font-weight: 700; margin-top: 8px; }
+.stat-card .label { color: var(--text-secondary); font-size: 0.85rem; }
+.stat-card .value { display: block; font-size: 1.75rem; font-weight: 700; margin-top: 8px; color: var(--text-primary); }
 
 /* Table */
 .claims-table-container {
-    background: rgba(255, 255, 255, 0.03);
+    background: var(--bg-card);
     border-radius: 16px;
-    border: 1px solid rgba(255, 255, 255, 0.05);
+    border: 1px solid var(--border-color);
     overflow: hidden;
+    box-shadow: var(--shadow-sm);
 }
 
 .claims-table {
@@ -158,25 +187,27 @@ h1 { font-size: 2rem; margin: 0; }
 
 .claims-table th {
     padding: 1.25rem 1rem;
-    color: #a0a0c0;
-    font-weight: 500;
+    color: var(--text-secondary);
+    font-weight: 600;
     font-size: 0.85rem;
-    background: rgba(255, 255, 255, 0.02);
+    background: var(--bg-primary);
+    border-bottom: 2px solid var(--border-color);
 }
 
 .claims-table td {
     padding: 1.25rem 1rem;
-    border-bottom: 1px solid rgba(255, 255, 255, 0.03);
+    border-bottom: 1px solid var(--border-color);
+    color: var(--text-primary);
 }
 
 .clickable-row { cursor: pointer; transition: background 0.2s; }
-.clickable-row:hover { background: rgba(255, 255, 255, 0.02); }
+.clickable-row:hover { background: var(--bg-primary); }
 
 .user-cell { display: flex; flex-direction: column; }
-.user-name { font-weight: 600; font-size: 0.9rem; }
-.user-dept { font-size: 0.75rem; color: #a0a0c0; }
+.user-name { font-weight: 600; font-size: 0.9rem; color: var(--text-primary); }
+.user-dept { font-size: 0.75rem; color: var(--text-secondary); }
 
-.capped-text { color: #ff4d4f; font-weight: 600; }
+.capped-text { color: #ef4444; font-weight: 600; }
 
 .status-pill {
     padding: 4px 10px;
@@ -185,8 +216,8 @@ h1 { font-size: 2rem; margin: 0; }
     font-weight: 600;
 }
 
-.status-pill.verified { background: rgba(0, 230, 118, 0.1); color: #00e676; }
-.status-pill.review { background: rgba(255, 165, 0, 0.1); color: #ffa500; }
+.status-pill.verified { background: rgba(34, 197, 94, 0.1); color: #166534; }
+.status-pill.review { background: rgba(245, 158, 11, 0.1); color: #92400e; }
 
 /* Modal */
 .modal-overlay {
@@ -195,8 +226,8 @@ h1 { font-size: 2rem; margin: 0; }
     left: 0;
     width: 100%;
     height: 100%;
-    background: rgba(0, 0, 0, 0.8);
-    backdrop-filter: blur(8px);
+    background: rgba(15, 23, 42, 0.4);
+    backdrop-filter: blur(4px);
     display: flex;
     justify-content: center;
     align-items: center;
@@ -204,23 +235,32 @@ h1 { font-size: 2rem; margin: 0; }
 }
 
 .modal-content {
-    background: #12121e;
+    background: var(--bg-card);
     width: 90%;
-    max-width: 800px;
+    max-width: 900px;
+    max-height: 90vh;
+    overflow-y: auto;
     border-radius: 24px;
     padding: 2.5rem;
     position: relative;
-    border: 1px solid rgba(255, 255, 255, 0.1);
+    border: 1px solid var(--border-color);
+    box-shadow: var(--shadow-md);
 }
 
 .close-btn {
     position: absolute;
     top: 1.5rem;
     right: 1.5rem;
-    background: transparent;
-    border: none;
-    color: #a0a0c0;
+    background: var(--bg-primary);
+    border: 1px solid var(--border-color);
+    color: var(--text-secondary);
+    width: 32px;
+    height: 32px;
+    border-radius: 50%;
     cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
 }
 
 .audit-grid {
@@ -231,28 +271,35 @@ h1 { font-size: 2rem; margin: 0; }
 }
 
 .receipt-audit {
-    background: rgba(0, 0, 0, 0.3);
+    background: var(--bg-primary);
     padding: 1.5rem;
     border-radius: 12px;
+    border: 1px solid var(--border-color);
 }
 
 .financial-audit {
-    background: rgba(255, 255, 255, 0.05);
+    background: var(--bg-card);
     padding: 1.5rem;
     border-radius: 12px;
+    border: 1px solid var(--border-color);
 }
 
 .audit-item {
     display: flex;
     justify-content: space-between;
     margin-bottom: 1rem;
+    color: var(--text-primary);
 }
 
+.audit-item .label { color: var(--text-secondary); }
+
 .audit-item.total {
     margin-top: 1.5rem;
+    padding-top: 1.5rem;
+    border-top: 2px solid var(--border-color);
     font-size: 1.25rem;
     font-weight: bold;
-    color: #00e676;
+    color: var(--accent-primary);
 }
 
 .ai-confidence-box {
@@ -260,17 +307,44 @@ h1 { font-size: 2rem; margin: 0; }
 }
 
 .conf-bar {
-    height: 6px;
-    background: rgba(255, 255, 255, 0.1);
-    border-radius: 3px;
-    margin: 10px 0;
+    height: 8px;
+    background: var(--bg-primary);
+    border-radius: 4px;
+    margin: 12px 0;
 }
 
 .conf-fill {
     height: 100%;
-    background: #4facfe;
-    border-radius: 3px;
+    background: var(--accent-primary);
+    border-radius: 4px;
 }
 
 .icon-tiny { width: 14px; height: 14px; }
 .icon-small { width: 20px; height: 20px; }
+.icon-medium { width: 24px; height: 24px; }
+
+.mock-receipt-view {
+    min-height: 400px;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    text-align: center;
+    color: var(--text-secondary);
+}
+
+.extraction-summary {
+    width: 100%;
+    text-align: left;
+    margin-top: 2rem;
+}
+
+.extraction-summary ul {
+    padding-left: 1.5rem;
+}
+
+.extraction-summary li {
+    margin-bottom: 0.5rem;
+    font-size: 0.9rem;
+    color: var(--text-primary);
+}

+ 60 - 32
ai-data-entry-ui/src/app/components/claims-dashboard/claims-dashboard.component.html

@@ -7,7 +7,7 @@
     </div>
     
     <nav>
-      <a routerLink="/dashboard" class="nav-item active">Dashboard</a>
+      <a routerLink="/" class="nav-item active">Dashboard</a>
       <a routerLink="/user-management" class="nav-item">
         <lucide-icon [name]="UserCog" class="icon-small"></lucide-icon> Users
       </a>
@@ -29,21 +29,26 @@
   <main class="content">
     <header class="content-header">
       <div>
-        <h1>Claims Intelligence</h1>
-        <p>Monitor and audit employee medical expenditure</p>
+        <h1>Global Stats</h1>
+        <p>Monitor and audit medical expenditure across the organization</p>
+      </div>
+      <div class="quick-actions">
+        <button class="secondary-btn" routerLink="/user-management">
+          <lucide-icon [name]="UserCog" class="icon-small"></lucide-icon> Manage Employees
+        </button>
+        <button class="action-btn" routerLink="/user-selection">
+          <lucide-icon [name]="Plus" class="icon-small"></lucide-icon> File New Claim
+        </button>
       </div>
-      <button class="action-btn" routerLink="/extract">
-        <lucide-icon [name]="Plus" class="icon-small"></lucide-icon> New Claim
-      </button>
     </header>
 
     <div class="stats-overview">
       <div class="stat-card">
-        <span class="label">Total Claims</span>
+        <span class="label">Total Claims (Session)</span>
         <span class="value">{{ claims.length }}</span>
       </div>
       <div class="stat-card">
-        <span class="label">Total Claimed</span>
+        <span class="label">Total Medical Payouts</span>
         <span class="value">RM {{ totalClaimed | number:'1.2-2' }}</span>
       </div>
     </div>
@@ -54,8 +59,8 @@
           <tr>
             <th>Employee</th>
             <th>Provider</th>
-            <th>Date</th>
-            <th>Spent (AI)</th>
+            <th>Visit Date</th>
+            <th>Spent (Receipt)</th>
             <th>Claimed (Capped)</th>
             <th>Status</th>
             <th>Audit</th>
@@ -69,15 +74,20 @@
                 <span class="user-dept">{{ claim.department }}</span>
               </div>
             </td>
-            <td>{{ claim.extraction_data.provider_name }}</td>
-            <td>{{ claim.extraction_data.visit_date | date:'mediumDate' }}</td>
+            <td>{{ claim.provider_name || claim.extraction_data?.provider_name || 'N/A' }}</td>
+            <td>{{ (claim.visit_date || claim.extraction_data?.visit_date) | date:'mediumDate' }}</td>
             <td>RM {{ claim.amount_spent | number:'1.2-2' }}</td>
             <td [class.capped-text]="claim.amount_claimed < claim.amount_spent">
               RM {{ claim.amount_claimed | number:'1.2-2' }}
             </td>
             <td>
-              <span class="status-pill" [class.verified]="!claim.extraction_data.needs_manual_review" [class.review]="claim.extraction_data.needs_manual_review">
-                {{ claim.extraction_data.needs_manual_review ? 'In Review' : 'Verified' }}
+              <span class="status-pill" *ngIf="claim.extraction_data"
+                   [class.verified]="!claim.extraction_data.needs_manual_review" 
+                   [class.review]="claim.extraction_data.needs_manual_review">
+                {{ claim.extraction_data.needs_manual_review ? 'In Review' : 'AI Verified' }}
+              </span>
+              <span class="status-pill manually-verified" style="background: rgba(148, 163, 184, 0.1); color: #475569;" *ngIf="!claim.extraction_data">
+                Manual Entry
               </span>
             </td>
             <td>
@@ -100,23 +110,30 @@
       
       <div class="modal-header">
         <h2>Claim Audit Details</h2>
-        <span class="claim-id">ID: {{ selectedClaim.id.substring(0,8) }}</span>
+        <span class="claim-id">Transaction ID: {{ selectedClaim.id.substring(0,8) }}</span>
       </div>
 
       <div class="modal-body">
         <div class="audit-grid">
-          <div class="receipt-audit">
-            <h3>Original Evidence</h3>
+          <div class="receipt-audit" *ngIf="selectedClaim.extraction_data">
+            <h3>Evidence Preview</h3>
             <div class="mock-receipt-view">
-              <p>Receipt data verified by OpenAI 4o-mini</p>
-              <div class="extraction-summary">
-                 <h4>AI Extracted Items:</h4>
-                 <ul>
-                   <li *ngFor="let item of selectedClaim.extraction_data.items">{{ item }}</li>
-                 </ul>
-              </div>
+              <lucide-icon [name]="FileText" class="icon-large" style="margin-bottom: 1rem; color: var(--text-secondary)"></lucide-icon>
+              <p>Medical Receipt Evidence</p>
+              <p style="font-size: 0.8rem">Verified by OpenAI 4o-mini</p>
+            </div>
+            <div class="extraction-summary">
+               <h4>AI Extracted Items:</h4>
+               <ul>
+                 <li *ngFor="let item of selectedClaim.extraction_data.items">{{ item }}</li>
+               </ul>
             </div>
           </div>
+          <div class="receipt-audit" *ngIf="!selectedClaim.extraction_data" style="display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center;">
+             <lucide-icon [name]="FileText" class="icon-large" style="margin-bottom: 1rem; color: var(--text-secondary); opacity: 0.5;"></lucide-icon>
+             <h3 style="margin-bottom: 0.5rem; color: var(--text-secondary)">Manual Entry</h3>
+             <p style="font-size: 0.85rem; color: var(--text-secondary)">No AI assistant or receipt evidence was provided for this claim.</p>
+          </div>
 
           <div class="financial-audit">
             <h3>Policy Enforcement</h3>
@@ -128,18 +145,29 @@
               <span class="label">Policy Ceiling:</span>
               <span class="val">{{ selectedClaim.amount_claimed < selectedClaim.amount_spent ? 'Capped' : 'N/A' }}</span>
             </div>
-            <hr>
+            <div class="audit-item" *ngIf="selectedClaim.amount_claimed < selectedClaim.amount_spent">
+              <span class="label">Deduction:</span>
+              <span class="val" style="color: #ef4444">- RM {{ (selectedClaim.amount_spent - selectedClaim.amount_claimed) | number:'1.2-2' }}</span>
+            </div>
+            
             <div class="audit-item total">
-              <span class="label">Total Paid:</span>
+              <span class="label">Final Payout:</span>
               <span class="val">RM {{ selectedClaim.amount_claimed | number:'1.2-2' }}</span>
             </div>
 
-            <div class="ai-confidence-box">
-               <span class="label">AI Confidence Score</span>
-               <div class="conf-bar">
-                 <div class="conf-fill" [style.width.%]="selectedClaim.extraction_data.confidence_score * 100"></div>
-               </div>
-               <span class="conf-val">{{ (selectedClaim.extraction_data.confidence_score * 100).toFixed(0) }}%</span>
+            <div *ngIf="selectedClaim.extraction_data">
+              <div class="ai-confidence-box">
+                 <span class="label">AI Confidence Score</span>
+                 <div class="conf-bar">
+                   <div class="conf-fill" [style.width.%]="selectedClaim.extraction_data.confidence_score * 100"></div>
+                 </div>
+                 <span class="conf-val">{{ (selectedClaim.extraction_data.confidence_score * 100).toFixed(1) }}% Match</span>
+              </div>
+
+              <div class="ai-reasoning-summary" style="margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color)">
+                <h4 style="font-size: 0.9rem; margin-bottom: 0.5rem">AI Reasoning:</h4>
+                <p style="font-size: 0.85rem; color: var(--text-secondary); font-style: italic">"{{ selectedClaim.extraction_data.ai_reasoning }}"</p>
+              </div>
             </div>
           </div>
         </div>

+ 2 - 5
ai-data-entry-ui/src/app/components/claims-dashboard/claims-dashboard.component.ts

@@ -4,7 +4,7 @@ import { Router, RouterModule } from '@angular/router';
 import { ExtractionService } from '../../services/extraction.service';
 import { SessionService, User } from '../../services/session.service';
 import { ClaimRecord } from '../../services/extraction';
-import { LucideAngularModule, LogOut, Plus, UserCog, ExternalLink, X } from 'lucide-angular';
+import { LucideAngularModule, LogOut, Plus, UserCog, ExternalLink, X, FileText } from 'lucide-angular';
 
 @Component({
   selector: 'app-dashboard',
@@ -25,6 +25,7 @@ export class DashboardComponent implements OnInit {
   readonly UserCog = UserCog;
   readonly ExternalLink = ExternalLink;
   readonly X = X;
+  readonly FileText = FileText;
 
   constructor(
     private extractionService: ExtractionService,
@@ -34,10 +35,6 @@ export class DashboardComponent implements OnInit {
 
   ngOnInit(): void {
     this.currentUser = this.sessionService.getCurrentUser();
-    if (!this.currentUser) {
-      this.router.navigate(['/']);
-      return;
-    }
     this.loadClaims();
   }
 

+ 32 - 23
ai-data-entry-ui/src/app/components/user-management/user-management.component.css

@@ -3,8 +3,8 @@
   max-width: 1200px;
   margin: 0 auto;
   font-family: 'Inter', sans-serif;
-  color: #fff;
-  background-color: #12121e;
+  color: var(--text-primary);
+  background-color: var(--bg-primary);
   min-height: 100vh;
 }
 
@@ -15,14 +15,12 @@ header {
 h1 {
   font-size: 2.5rem;
   margin-top: 1rem;
-  background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);
-  -webkit-background-clip: text;
-  background-clip: text;
-  -webkit-text-fill-color: transparent;
+  color: var(--text-primary);
+  font-weight: 800;
 }
 
 header p {
-  color: #a0a0c0;
+  color: var(--text-secondary);
 }
 
 .management-grid {
@@ -32,16 +30,18 @@ header p {
 }
 
 .card {
-  background: rgba(255, 255, 255, 0.05);
+  background: var(--bg-card);
   border-radius: 16px;
   padding: 2rem;
-  border: 1px solid rgba(255, 255, 255, 0.1);
+  border: 1px solid var(--border-color);
+  box-shadow: var(--shadow-sm);
 }
 
 h2 {
   font-size: 1.25rem;
   margin-bottom: 1.5rem;
-  color: #fff;
+  color: var(--text-primary);
+  font-weight: 700;
 }
 
 .form-field {
@@ -51,31 +51,34 @@ h2 {
 .form-field label {
   display: block;
   margin-bottom: 0.5rem;
-  color: #a0a0c0;
+  color: var(--text-secondary);
   font-size: 0.9rem;
+  font-weight: 500;
 }
 
 input {
   width: 100%;
   padding: 0.75rem;
-  background: rgba(0, 0, 0, 0.2);
-  border: 1px solid rgba(255, 255, 255, 0.1);
+  background: var(--bg-primary);
+  border: 1px solid var(--border-color);
   border-radius: 8px;
-  color: #fff;
+  color: var(--text-primary);
 }
 
 .submit-btn {
   width: 100%;
-  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
+  background: linear-gradient(90deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
   border: none;
   padding: 1rem;
   border-radius: 8px;
+  color: #fff;
   font-weight: 600;
   cursor: pointer;
   display: flex;
   align-items: center;
   justify-content: center;
   gap: 0.5rem;
+  box-shadow: var(--shadow-sm);
 }
 
 .user-list {
@@ -88,14 +91,17 @@ input {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  background: rgba(255, 255, 255, 0.03);
+  background: var(--bg-primary);
   padding: 1rem;
   border-radius: 12px;
-  transition: background 0.2s;
+  transition: all 0.2s;
+  border: 1px solid transparent;
 }
 
 .user-item:hover {
-  background: rgba(255, 255, 255, 0.07);
+  background: #fff;
+  border-color: var(--border-color);
+  box-shadow: var(--shadow-sm);
 }
 
 .user-info {
@@ -107,7 +113,8 @@ input {
 .user-avatar-small {
   width: 40px;
   height: 40px;
-  background: #4facfe;
+  background: var(--accent-primary);
+  color: #fff;
   border-radius: 50%;
   display: flex;
   align-items: center;
@@ -118,12 +125,13 @@ input {
 .user-info h3 {
   margin: 0;
   font-size: 1rem;
+  color: var(--text-primary);
 }
 
 .user-info p {
   margin: 0;
   font-size: 0.8rem;
-  color: #a0a0c0;
+  color: var(--text-secondary);
 }
 
 .user-balance {
@@ -134,23 +142,24 @@ input {
 
 .user-balance .label {
   font-size: 0.75rem;
-  color: #a0a0c0;
+  color: var(--text-secondary);
 }
 
 .user-balance .value {
   font-weight: bold;
-  color: #00e676;
+  color: #166534;
 }
 
 .back-btn {
   background: transparent;
   border: none;
-  color: #4facfe;
+  color: var(--accent-primary);
   cursor: pointer;
   display: flex;
   align-items: center;
   gap: 0.5rem;
   padding: 0;
+  font-weight: 600;
 }
 
 .icon-small {

+ 1 - 1
ai-data-entry-ui/src/app/components/user-management/user-management.component.html

@@ -1,6 +1,6 @@
 <div class="management-container">
   <header>
-    <button class="back-btn" routerLink="/dashboard">
+    <button class="back-btn" routerLink="/">
       <lucide-icon [name]="ArrowLeft" class="icon-small"></lucide-icon> Back to Dashboard
     </button>
     <h1>User Management</h1>

+ 25 - 27
ai-data-entry-ui/src/app/components/user-selection/user-selection.component.css

@@ -3,57 +3,55 @@
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  height: 100vh;
-  background: linear-gradient(135deg, #1e1e2f 0%, #2a2a40 100%);
-  color: #fff;
+  min-height: 100vh;
+  background-color: var(--bg-primary);
+  color: var(--text-primary);
   font-family: 'Inter', sans-serif;
   text-align: center;
+  padding: 2rem;
 }
 
 h1 {
   font-size: 2.5rem;
   margin-bottom: 0.5rem;
-  background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);
-  -webkit-background-clip: text;
-  background-clip: text;
-  -webkit-text-fill-color: transparent;
+  color: var(--text-primary);
+  font-weight: 800;
 }
 
 p {
-  color: #a0a0c0;
+  color: var(--text-secondary);
   margin-bottom: 3rem;
+  font-size: 1.1rem;
 }
 
 .user-grid {
   display: grid;
-  grid-template-columns: repeat(3, 1fr);
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
   gap: 2rem;
-  max-width: 900px;
+  max-width: 1000px;
   width: 100%;
-  padding: 0 2rem;
 }
 
 .user-card {
-  background: rgba(255, 255, 255, 0.05);
-  border: 1px solid rgba(255, 255, 255, 0.1);
-  border-radius: 16px;
-  padding: 2rem;
+  background: var(--bg-card);
+  border: 1px solid var(--border-color);
+  border-radius: 20px;
+  padding: 2.5rem;
   cursor: pointer;
-  transition: all 0.3s ease;
-  backdrop-filter: blur(10px);
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  box-shadow: var(--shadow-sm);
 }
 
 .user-card:hover {
-  transform: translateY(-10px);
-  background: rgba(255, 255, 255, 0.1);
-  border-color: #4facfe;
-  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+  transform: translateY(-8px);
+  border-color: var(--accent-primary);
+  box-shadow: var(--shadow-md);
 }
 
 .user-avatar {
   width: 80px;
   height: 80px;
-  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+  background: linear-gradient(135deg, var(--accent-primary) 0%, var(--accent-secondary) 100%);
   border-radius: 50%;
   margin: 0 auto 1.5rem;
   display: flex;
@@ -62,24 +60,24 @@ p {
   font-size: 2rem;
   font-weight: bold;
   color: #fff;
-  box-shadow: 0 4px 15px rgba(79, 172, 254, 0.4);
+  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
 }
 
 .user-info h3 {
   margin: 0;
   font-size: 1.25rem;
-  color: #fff;
+  color: var(--text-primary);
+  font-weight: 700;
 }
 
 .user-info p {
-  margin: 0.25rem 0 0;
+  margin: 0.5rem 0 0;
   font-size: 0.9rem;
-  color: #a0a0c0;
+  color: var(--text-secondary);
 }
 
 @media (max-width: 768px) {
   .user-grid {
     grid-template-columns: 1fr;
-    gap: 1.5rem;
   }
 }

+ 1 - 1
ai-data-entry-ui/src/app/components/user-selection/user-selection.component.ts

@@ -48,6 +48,6 @@ export class UserSelectionComponent implements OnInit {
       medical_allowance: user.medical_allowance
     };
     this.sessionService.setCurrentUser(sessionUser);
-    this.router.navigate(['/dashboard']);
+    this.router.navigate(['/new-claim']);
   }
 }

+ 3 - 3
ai-data-entry-ui/src/app/services/extraction.service.ts

@@ -1,7 +1,7 @@
 import { Injectable } from '@angular/core';
 import { HttpClient, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
 import { Observable, catchError, throwError } from 'rxjs';
-import { ExtractionResponse, ClaimRecord, UserAccount } from './extraction';
+import { ExtractionResponse, ClaimRecord, UserAccount, ClaimSubmission } from './extraction';
 
 @Injectable({
   providedIn: 'root'
@@ -37,12 +37,12 @@ export class ExtractionService {
       );
   }
 
-  submitClaim(extractionData: ExtractionResponse, userId: string): Observable<ClaimRecord> {
+  submitClaim(submissionData: ClaimSubmission, userId: string): Observable<ClaimRecord> {
     const headers = new HttpHeaders({
       'user-id': userId
     });
 
-    return this.http.post<ClaimRecord>(`${this.apiUrl}/claims`, extractionData, { headers })
+    return this.http.post<ClaimRecord>(`${this.apiUrl}/claims`, submissionData, { headers })
       .pipe(
         catchError(this.handleError)
       );

+ 11 - 1
ai-data-entry-ui/src/app/services/extraction.ts

@@ -16,6 +16,14 @@ export interface ExtractionResponse {
   ai_reasoning: string;
 }
 
+export interface ClaimSubmission {
+  provider_name: string;
+  visit_date: string;
+  amount_spent: number;
+  currency: string;
+  extraction_data?: ExtractionResponse | null;
+}
+
 export interface ClaimRecord {
   id: string;
   timestamp: string;
@@ -23,5 +31,7 @@ export interface ClaimRecord {
   department: string;
   amount_spent: number;
   amount_claimed: number;
-  extraction_data: ExtractionResponse;
+  provider_name?: string;
+  visit_date?: string;
+  extraction_data?: ExtractionResponse | null;
 }

+ 18 - 1
ai-data-entry-ui/src/styles.css

@@ -1 +1,18 @@
-/* You can add global styles to this file, and also import other style files */
+:root {
+  --bg-primary: #f8fafc;
+  --bg-card: #ffffff;
+  --text-primary: #1e293b;
+  --text-secondary: #64748b;
+  --border-color: #e2e8f0;
+  --accent-primary: #3b82f6;
+  --accent-secondary: #60a5fa;
+  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+}
+
+body {
+  background-color: var(--bg-primary);
+  color: var(--text-primary);
+  margin: 0;
+  font-family: 'Inter', system-ui, -apple-system, sans-serif;
+}

+ 6 - 4
backend/main.py

@@ -5,7 +5,7 @@ from fastapi import FastAPI, UploadFile, File, Header, HTTPException, status, Fo
 from fastapi.middleware.cors import CORSMiddleware
 from typing import Optional, List
 from backend.services.openai_service import extract_receipt_data
-from backend.schemas import ExtractionResponse, ClaimRecord, UserAccount
+from backend.schemas import ExtractionResponse, ClaimRecord, UserAccount, ClaimSubmission
 from dotenv import load_dotenv
 
 load_dotenv()
@@ -69,7 +69,7 @@ async def extract_receipt(
 
 @app.post("/api/v1/claims", response_model=ClaimRecord)
 async def submit_claim(
-    extraction_data: ExtractionResponse,
+    submission_data: ClaimSubmission,
     user_id: str = Header(...)
 ):
     # 1. Find User
@@ -78,7 +78,7 @@ async def submit_claim(
         raise HTTPException(status_code=404, detail="User not found")
 
     # 2. Financial Guard Logic
-    spent_amount = extraction_data.total_amount
+    spent_amount = submission_data.amount_spent
     remaining = user.medical_allowance
     amount_claimed = min(spent_amount, remaining)
 
@@ -93,7 +93,9 @@ async def submit_claim(
         department=user.department,
         amount_spent=spent_amount,
         amount_claimed=amount_claimed,
-        extraction_data=extraction_data
+        provider_name=submission_data.provider_name,
+        visit_date=submission_data.visit_date,
+        extraction_data=submission_data.extraction_data
     )
     CLAIMS_DB.append(claim)
     return claim

+ 11 - 2
backend/schemas.py

@@ -19,11 +19,20 @@ class ExtractionResponse(BaseModel):
     needs_manual_review: bool = Field(description="Set to true if text is blurry or data is ambiguous.")
     ai_reasoning: str = Field(description="A brief explanation of how the data was identified.")
 
+class ClaimSubmission(BaseModel):
+    provider_name: str = Field(description="The name of the clinic or hospital.")
+    visit_date: str = Field(description="The date of service in YYYY-MM-DD format.")
+    amount_spent: float = Field(description="The manual or validated total amount spent.")
+    currency: str = Field(description="3-letter currency code (e.g. USD, MYR).")
+    extraction_data: Optional[ExtractionResponse] = Field(default=None, description="Optional AI extraction data if used.")
+
 class ClaimRecord(BaseModel):
     id: str = Field(description="Unique identifier for the claim record.")
     timestamp: str = Field(description="ISO format timestamp of submission.")
     submitted_by: str = Field(description="Name of the user who submitted the claim.")
     department: str = Field(description="Department of the user.")
-    amount_spent: float = Field(description="The raw total amount from the receipt.")
+    amount_spent: float = Field(description="The raw total amount from the form/receipt.")
     amount_claimed: float = Field(description="The actual amount credited after policy capping.")
-    extraction_data: ExtractionResponse = Field(description="The AI-extracted data for this claim.")
+    provider_name: str = Field(description="The name of the clinic or hospital.")
+    visit_date: str = Field(description="The date of service in YYYY-MM-DD format.")
+    extraction_data: Optional[ExtractionResponse] = Field(default=None, description="The optional AI-extracted data for this claim.")