Dr-Swopt 1 неделя назад
Родитель
Сommit
7734203e6e

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

@@ -2,10 +2,12 @@ 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: '**', redirectTo: '' }
 ];

+ 179 - 185
ai-data-entry-ui/src/app/components/claim-form/claim-form.component.css

@@ -1,259 +1,253 @@
-.form-header {
-    max-width: 1200px;
-    margin: 10px auto;
-    padding: 0 20px;
+.split-screen-container {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    height: 100vh;
+    background-color: #0c0c14;
+    color: #fff;
+    font-family: 'Inter', sans-serif;
 }
 
-.back-btn {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    background: transparent;
-    border: none;
-    color: #4facfe;
-    cursor: pointer;
-    font-weight: 600;
-    font-size: 0.9rem;
-    padding: 10px 0;
-    transition: color 0.2s;
+.receipt-container {
+    padding: 2rem;
+    border-right: 1px solid rgba(255, 255, 255, 0.05);
+    overflow-y: auto;
+    background: #12121e;
 }
 
-.back-btn:hover {
-    color: #00f2fe;
+.form-container {
+    padding: 2.5rem;
+    overflow-y: auto;
 }
 
-.spinner-small {
-    font-size: 0.85rem;
-    color: #4facfe;
-    font-weight: 500;
+.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;
 }
 
-.receipt-container { 
-    flex: 1; 
-    border-right: 2px solid #eee; 
-    padding-right: 40px;
-    display: flex;
-    flex-direction: column;
+.spent-hero {
+    margin-bottom: 2rem;
+    padding-bottom: 2rem;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
 }
 
-.form-container { 
-    flex: 1; 
+.hero-label {
+    text-transform: uppercase;
+    font-size: 0.75rem;
+    letter-spacing: 0.1em;
+    color: #a0a0c0;
 }
 
-.preview-wrapper {
-    position: relative;
-    width: 100%;
-    min-height: 400px;
-    background: #f9f9f9;
-    border: 2px dashed #ccc;
-    border-radius: 8px;
+.hero-hint {
+    font-size: 0.85rem;
+    color: #666;
+}
+
+.deduction-warning {
+    background: rgba(255, 77, 79, 0.1);
+    border: 1px solid #ff4d4f;
+    border-radius: 12px;
+    padding: 1.25rem;
     display: flex;
-    justify-content: center;
-    align-items: center;
-    overflow: hidden;
-    transition: all 0.3s ease;
+    gap: 1rem;
+    margin-bottom: 2rem;
 }
 
-.preview-wrapper.dragging {
-    background: rgba(79, 172, 254, 0.1);
-    border-color: #4facfe;
-    transform: scale(1.02);
+.deduction-warning h3 {
+    margin: 0;
+    font-size: 1rem;
+    color: #ff4d4f;
 }
 
-.receipt-preview { 
-    width: 100%; 
-    height: auto;
-    border-radius: 8px; 
-    box-shadow: 0 4px 12px rgba(0,0,0,0.1); 
-    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-    cursor: zoom-in;
+.deduction-warning p {
+    margin: 5px 0 0;
+    font-size: 0.9rem;
+    color: #ff7875;
 }
 
-.receipt-preview:hover {
-    transform: scale(1.1);
+.form-field {
+    margin-bottom: 1.5rem;
 }
 
-.placeholder {
-    color: #888;
-    text-align: center;
-    padding: 20px;
+.form-field label {
+    display: block;
+    margin-bottom: 0.5rem;
+    color: #a0a0c0;
+    font-size: 0.9rem;
 }
 
-.review-banner {
-    position: absolute;
-    top: 0;
-    left: 0;
+.form-field input {
     width: 100%;
-    background: #ff4d4f;
-    color: white;
-    padding: 10px;
-    text-align: center;
-    font-weight: bold;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: 10px;
-    z-index: 10;
+    padding: 0.75rem;
+    background: rgba(255, 255, 255, 0.03);
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    border-radius: 8px;
+    color: #fff;
+    font-size: 1rem;
 }
 
-.review-banner.amber {
-    background: #ffa500;
+.form-field input:focus {
+    border-color: #4facfe;
+    outline: none;
 }
 
-.error-banner {
-    position: absolute;
-    bottom: 0;
-    left: 0;
+.submit-btn {
     width: 100%;
-    background: #ff4d4f;
-    color: white;
-    padding: 10px;
-    text-align: center;
-    font-size: 0.9rem;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    gap: 8px;
-    z-index: 20;
+    padding: 1rem;
+    border-radius: 8px;
+    border: none;
+    background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
+    color: #fff;
+    font-weight: 700;
+    cursor: pointer;
 }
 
-.upload-section {
-    margin-top: 20px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    gap: 10px;
+.transparency-section {
+    margin: 2rem 0;
 }
 
-.upload-btn {
-    background: #007bff;
-    color: white;
-    border: none;
-    padding: 12px 24px;
+.debug-toggle {
+    background: transparent;
+    border: 1px solid rgba(255, 255, 255, 0.1);
+    color: #a0a0c0;
+    padding: 0.5rem 1rem;
     border-radius: 6px;
     cursor: pointer;
-    font-weight: 600;
-    transition: background 0.2s;
-}
-
-.upload-btn:hover {
-    background: #0056b3;
+    display: flex;
+    align-items: center;
+    gap: 8px;
 }
 
-.form-field {
-    margin-bottom: 20px;
-    padding: 15px;
-    border-radius: 8px;
-    transition: all 0.3s ease;
+.raw-json-view {
+    margin-top: 1.5rem;
+    background: #000;
+    padding: 1.5rem;
+    border-radius: 12px;
+    font-size: 0.8rem;
 }
 
-.form-field label {
-    display: block;
-    margin-bottom: 8px;
-    font-weight: 500;
-    color: #333;
+.ai-reasoning-box {
+    border-bottom: 1px solid #333;
+    padding-bottom: 1rem;
+    margin-bottom: 1rem;
 }
 
-.form-field input, .form-field select {
-    width: 100%;
-    padding: 10px;
-    border: 1px solid #ddd;
-    border-radius: 4px;
-    box-sizing: border-box;
+.ai-reasoning-box h4 {
+    margin: 0 0 0.5rem;
+    color: #4facfe;
 }
 
-.amount-input {
-    display: flex;
-    gap: 10px;
+.ai-reasoning-box p {
+    color: #fff;
+    font-style: italic;
+    line-height: 1.5;
 }
 
-.amount-input input {
-    flex: 3;
+.raw-json-view pre {
+    color: #00e676;
+    margin: 0;
+    overflow-x: auto;
 }
 
-.amount-input select {
-    flex: 1;
+.preview-wrapper {
+    width: 100%;
+    min-height: 400px;
+    background: rgba(255, 255, 255, 0.02);
+    border: 2px dashed rgba(255, 255, 255, 0.1);
+    border-radius: 16px;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    margin-top: 1rem;
 }
 
-.ai-unconfident { 
-    border: 2px solid #ffa500 !important; 
-    background-color: #fff9e6; 
+.receipt-preview {
+    max-width: 100%;
+    max-height: 600px;
 }
 
-.confidence-badge {
-    margin-top: 8px;
-    font-size: 0.8rem;
-    color: #666;
-    font-style: italic;
-}.submit-btn {
-    width: 100%;
-    background: #28a745;
-    color: white;
+.upload-trigger {
+    background: #4facfe;
+    color: #fff;
     border: none;
-    padding: 15px;
-    border-radius: 6px;
+    padding: 0.75rem 1.5rem;
+    border-radius: 8px;
     cursor: pointer;
     font-weight: 600;
-    margin-top: 20px;
+    margin-top: 1rem;
 }
 
-.submit-btn:disabled {
-    background: #ccc;
-    cursor: not-allowed;
+.user-selector-field select {
+    width: 100%;
+    padding: 1rem;
+    background: #1a1a2e;
+    color: #fff;
+    border: 1px solid #4facfe;
+    border-radius: 12px;
 }
 
-.status-badge-container {
-    display: flex;
-    justify-content: center;
-    margin-bottom: 10px;
+.back-btn {
+    background: transparent;
+    border: none;
+    color: #a0a0c0;
+    cursor: pointer;
+    margin-bottom: 1rem;
 }
 
-.status-badge {
-    padding: 6px 12px;
-    border-radius: 20px;
-    font-size: 0.85rem;
-    font-weight: 600;
+.active-user-badge {
     display: flex;
     align-items: center;
-    gap: 6px;
-}
-
-.status-badge.passed {
-    background: #e6f7ed;
-    color: #28a745;
-    border: 1px solid #28a745;
-}
-
-.status-badge.review {
-    background: #fff4e6;
-    color: #ffa500;
-    border: 1px solid #ffa500;
+    gap: 15px;
+    background: rgba(79, 172, 254, 0.1);
+    padding: 15px;
+    border-radius: 12px;
+    border: 1px solid rgba(79, 172, 254, 0.3);
+    margin-top: 15px;
 }
 
-.warning-text {
-    color: #ffa500;
+.active-user-badge .avatar {
+    width: 40px;
+    height: 40px;
+    background: #4facfe;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
     font-weight: bold;
 }
 
-.icon-small {
-    width: 16px;
-    height: 16px;
+.active-user-badge .details {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
 }
 
+.active-user-badge .name { font-weight: 600; font-size: 1rem; }
+.active-user-badge .allowance { font-size: 0.8rem; color: #4facfe; }
 
-.spinner {
-    color: #007bff;
-    font-weight: 500;
+.change-user {
+    background: transparent;
+    border: 1px solid #4facfe;
+    color: #4facfe;
+    padding: 4px 12px;
+    border-radius: 6px;
+    cursor: pointer;
+    font-size: 0.8rem;
 }
 
-.currency-label {
-    padding: 10px;
-    background: #eee;
-    border: 1px solid #ddd;
-    border-left: none;
-    border-radius: 0 4px 4px 0;
+.caps-banner {
+    margin-top: 10px !important;
+    background: #ff4d4f;
+    color: #fff !important;
+    display: inline-block;
+    padding: 2px 8px;
+    border-radius: 4px;
+    font-size: 0.75rem !important;
     font-weight: bold;
-    color: #555;
-    display: flex;
-    align-items: center;
+    text-transform: uppercase;
 }

+ 85 - 75
ai-data-entry-ui/src/app/components/claim-form/claim-form.component.html

@@ -1,96 +1,106 @@
-<div class="form-header">
-  <button class="back-btn" routerLink="/dashboard">
-    <lucide-icon [name]="ArrowLeft" class="icon-small"></lucide-icon> Back to Dashboard
-  </button>
-</div>
-
-<div class="split-view">
+<div class="split-screen-container">
   <!-- Left Column: Receipt Preview -->
   <div class="receipt-container">
-    <h2>Receipt Preview</h2>
-    <div class="preview-wrapper" 
-         [class.dragging]="isDragging"
-         (dragover)="onDragOver($event)" 
-         (dragleave)="onDragLeave($event)" 
-         (drop)="onDrop($event)">
-      <img *ngIf="previewUrl" [src]="previewUrl" class="receipt-preview" alt="Receipt Preview">
-      <div *ngIf="!previewUrl" class="placeholder">
-        <p>Drag and drop a medical receipt here, or use the button below.</p>
-      </div>
-      
-      <!-- Enhanced Overlay for Manual Review -->
-      <div *ngIf="extractionResponse?.needs_manual_review" class="review-banner amber">
-        <lucide-icon [name]="AlertCircle" class="icon"></lucide-icon>
-        <span>Human Review Advised: Data validation triggered</span>
+    <div class="back-nav">
+      <button class="back-btn" routerLink="/dashboard">
+        <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>
+      <div class="active-user-badge">
+         <div class="avatar">{{ currentUser.name[0] }}</div>
+         <div class="details">
+            <span class="name">{{ currentUser.name }}</span>
+            <span class="allowance">Allowance Remaining: RM {{ currentUser.medical_allowance | number:'1.2-2' }}</span>
+         </div>
+         <button class="change-user" routerLink="/">Change</button>
       </div>
+    </header>
 
-      <div *ngIf="errorMessage" class="error-banner">
-        <lucide-icon [name]="AlertCircle" class="icon"></lucide-icon>
-        <span>{{ errorMessage }}</span>
+    <div class="receipt-viewer" *ngIf="currentUser">
+      <h2>Step 2: Upload Receipt</h2>
+      <div class="preview-wrapper" 
+           [class.dragging]="isDragging"
+           (dragover)="onDragOver($event)" 
+           (dragleave)="onDragLeave($event)" 
+           (drop)="onDrop($event)">
+        <img *ngIf="previewUrl" [src]="previewUrl" class="receipt-preview" alt="Receipt Preview">
+        <div *ngIf="!previewUrl" class="placeholder">
+          <p>Drag and drop a medical receipt here, or use the button below.</p>
+          <input type="file" #fileInput (change)="onFileSelected($event)" accept="image/*" hidden>
+          <button class="upload-trigger" (click)="fileInput.click()">
+            Select Receipt Image
+          </button>
+        </div>
+        
+        <div class="manual-review-overlay" *ngIf="extractionResponse?.needs_manual_review">
+          <div class="overlay-badge">
+             <lucide-icon [name]="AlertCircle" class="icon-small"></lucide-icon>
+             Manual Review Required
+          </div>
+        </div>
       </div>
     </div>
-    
-    <div class="upload-section">
-      <input type="file" #fileInput (change)="onFileSelected($event)" style="display: none" accept="image/*">
-      <button class="upload-btn" (click)="fileInput.click()" [disabled]="isLoading">
-        {{ previewUrl ? 'Change Receipt' : 'Upload Medical Receipt' }}
-      </button>
-      <div *ngIf="isLoading" class="spinner-small">Extracting data...</div>
-    </div>
   </div>
 
   <!-- Right Column: Data Entry Form -->
-  <div class="form-container">
-    <h2>Claim Information</h2>
+  <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>
+
+    <!-- SPECIFIC DEDUCTION WARNING -->
+    <div class="deduction-warning" *ngIf="claimForm.get('amount_claimed')?.value < extractionResponse.total_amount">
+       <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>
+       </div>
+    </div>
+
     <form [formGroup]="claimForm" (ngSubmit)="onSubmit()">
-      
-      <!-- Provider Name -->
-      <div class="form-field" [class.ai-unconfident]="extractionResponse && extractionResponse.confidence_score < 0.8">
-        <label for="provider_name">Provider Name</label>
-        <input id="provider_name" type="text" formControlName="provider_name" placeholder="Clinic or Hospital Name">
-        <div class="confidence-badge" *ngIf="extractionResponse">
-          AI Confidence: {{ (extractionResponse.confidence_score * 100).toFixed(0) }}%
-          <span *ngIf="extractionResponse.confidence_score < 0.8" class="warning-text"> - Low Confidence</span>
+      <div class="form-section">
+        <h3>Claim Details</h3>
+        <div class="form-field">
+          <label>Provider Name</label>
+          <input type="text" formControlName="provider_name">
         </div>
-      </div>
-
-      <!-- Visit Date -->
-      <div class="form-field" [class.ai-unconfident]="extractionResponse && extractionResponse.confidence_score < 0.8">
-        <label for="visit_date">Visit Date</label>
-        <input id="visit_date" type="date" formControlName="visit_date">
-        <div class="confidence-badge" *ngIf="extractionResponse">
-          AI Confidence: {{ (extractionResponse.confidence_score * 100).toFixed(0) }}%
-          <span *ngIf="extractionResponse.confidence_score < 0.8" class="warning-text"> - Low Confidence</span>
+        <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>
 
-      <!-- Total Amount -->
-      <div class="form-field" [class.ai-unconfident]="extractionResponse && extractionResponse.confidence_score < 0.8">
-        <label for="total_amount">Total Amount</label>
-        <div class="amount-input">
-          <input id="total_amount" type="number" formControlName="total_amount" step="0.01">
-          <span class="currency-label">{{ claimForm.get('currency')?.value }}</span>
-        </div>
-        <div class="confidence-badge" *ngIf="extractionResponse">
-          AI Confidence: {{ (extractionResponse.confidence_score * 100).toFixed(0) }}%
-          <span *ngIf="extractionResponse.confidence_score < 0.8" class="warning-text"> - Low Confidence</span>
-        </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">
-        <div class="status-badge-container" *ngIf="extractionResponse && !isLoading">
-          <div *ngIf="!extractionResponse.needs_manual_review" class="status-badge passed">
-            <lucide-icon [name]="ShieldCheck" class="icon-small"></lucide-icon>
-            AI Verification Passed
-          </div>
-          <div *ngIf="extractionResponse.needs_manual_review" class="status-badge review">
-            <lucide-icon [name]="AlertCircle" class="icon-small"></lucide-icon>
-            Human Review Advised
-          </div>
-        </div>
-        
         <button type="submit" [disabled]="!claimForm.valid || isLoading || isSubmitting" class="submit-btn" [class.loading]="isLoading || isSubmitting">
-           {{ isSubmitting ? 'Submitting...' : (isLoading ? 'ProcessingAI...' : 'Confirm and Submit Claim') }}
+           {{ isSubmitting ? 'Finalizing...' : 'Submit Official Claim' }}
         </button>
       </div>
     </form>

+ 38 - 22
ai-data-entry-ui/src/app/components/claim-form/claim-form.component.ts

@@ -3,9 +3,9 @@ import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angula
 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 } from 'lucide-angular';
-import { SessionService } from '../../services/session.service';
+import { LucideAngularModule, ShieldCheck, AlertCircle, CheckCircle, ArrowLeft, Eye, EyeOff } from 'lucide-angular';
 
 @Component({
   selector: 'app-claim-form',
@@ -16,15 +16,20 @@ import { SessionService } from '../../services/session.service';
 })
 export class ClaimFormComponent implements OnInit {
   claimForm: FormGroup;
+  currentUser: User | null = null;
   previewUrl: string | null = null;
   extractionResponse: ExtractionResponse | null = null;
   isLoading = false;
   isSubmitting = false;
   isDragging = false;
+  showRawJson = false;
+
   readonly ShieldCheck = ShieldCheck;
   readonly AlertCircle = AlertCircle;
   readonly CheckCircle = CheckCircle;
   readonly ArrowLeft = ArrowLeft;
+  readonly Eye = Eye;
+  readonly EyeOff = EyeOff;
   errorMessage: string | null = null;
 
   constructor(
@@ -36,18 +41,29 @@ export class ClaimFormComponent implements OnInit {
     this.claimForm = this.fb.group({
       provider_name: ['', Validators.required],
       visit_date: ['', Validators.required],
-      total_amount: [0, [Validators.required, Validators.min(0.01)]],
+      amount_spent: [{ value: 0, disabled: true }, [Validators.required, Validators.min(0.01)]],
+      amount_claimed: [{ value: 0, disabled: false }, [Validators.required, Validators.min(0)]],
       currency: ['MYR']
     });
   }
 
   ngOnInit(): void {
-    const user = this.sessionService.getCurrentUser();
-    if (!user) {
+    this.currentUser = this.sessionService.getCurrentUser();
+    if (!this.currentUser) {
       this.router.navigate(['/']);
     }
   }
 
+  calculateClaimable(): void {
+    if (!this.currentUser || !this.extractionResponse) return;
+    
+    const spent = 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 });
+  }
+
   onDragOver(event: DragEvent): void {
     event.preventDefault();
     event.stopPropagation();
@@ -64,7 +80,7 @@ export class ClaimFormComponent implements OnInit {
     event.preventDefault();
     event.stopPropagation();
     this.isDragging = false;
-
+    
     const files = event.dataTransfer?.files;
     if (files && files.length > 0) {
       this.processFile(files[0]);
@@ -79,21 +95,22 @@ export class ClaimFormComponent implements OnInit {
   }
 
   private processFile(file: File): void {
+    if (!this.currentUser) return;
+
     this.previewUrl = URL.createObjectURL(file);
     this.isLoading = true;
     this.errorMessage = null;
-
-    const user = this.sessionService.getCurrentUser();
-
-    this.extractionService.extractData(file, user?.name, user?.department).subscribe({
+    
+    this.extractionService.extractData(file, this.currentUser.name, this.currentUser.department).subscribe({
       next: (response) => {
         this.extractionResponse = response;
         this.claimForm.patchValue({
           provider_name: response.provider_name,
           visit_date: response.visit_date,
-          total_amount: response.total_amount,
+          amount_spent: response.total_amount,
           currency: response.currency || 'MYR'
         });
+        this.calculateClaimable();
         this.isLoading = false;
       },
       error: (err: Error) => {
@@ -105,21 +122,20 @@ export class ClaimFormComponent implements OnInit {
   }
 
   onSubmit(): void {
-    if (this.claimForm.invalid) return;
-
-    const user = this.sessionService.getCurrentUser();
-    if (!user) return;
+    if (this.claimForm.invalid || !this.currentUser) return;
 
     this.isSubmitting = true;
-    const formData = this.claimForm.value;
-
-    // Merge user-verified data with AI response metadata if needed
+    const formData = this.claimForm.getRawValue(); // To get disabled fields
+    
     const submissionData: ExtractionResponse = {
       ...this.extractionResponse!,
-      ...formData
+      provider_name: formData.provider_name,
+      visit_date: formData.visit_date,
+      total_amount: formData.amount_spent,
+      currency: formData.currency
     };
 
-    this.extractionService.submitClaim(submissionData, user.name, user.department).subscribe({
+    this.extractionService.submitClaim(submissionData, this.currentUser.id).subscribe({
       next: () => {
         this.isSubmitting = false;
         this.router.navigate(['/dashboard']);
@@ -132,7 +148,7 @@ export class ClaimFormComponent implements OnInit {
     });
   }
 
-  get confidenceScore(): number {
-    return this.extractionResponse?.confidence_score ?? 0;
+  get rawJson(): string {
+    return JSON.stringify(this.extractionResponse, null, 2);
   }
 }

+ 220 - 143
ai-data-entry-ui/src/app/components/claims-dashboard/claims-dashboard.component.css

@@ -1,199 +1,276 @@
-.dashboard-container {
-  padding: 2rem;
-  max-width: 1200px;
-  margin: 0 auto;
-  font-family: 'Inter', sans-serif;
-  color: #fff;
-  background-color: #12121e;
-  min-height: 100vh;
+.dashboard-wrapper {
+    display: flex;
+    min-height: 100vh;
+    background-color: #0c0c14;
+    color: #fff;
+    font-family: 'Inter', sans-serif;
 }
 
-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 2rem;
-  padding-bottom: 1rem;
-  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+/* Sidebar */
+.sidebar {
+    width: 260px;
+    background-color: #12121e;
+    border-right: 1px solid rgba(255, 255, 255, 0.05);
+    padding: 2rem 1.5rem;
+    display: flex;
+    flex-direction: column;
 }
 
-h1 {
-  font-size: 1.8rem;
-  margin: 0;
-  background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);
-  -webkit-background-clip: text;
-  background-clip: text;
-  -webkit-text-fill-color: transparent;
+.logo {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    font-size: 1.5rem;
+    font-weight: 800;
+    margin-bottom: 3rem;
 }
 
-header p {
-  color: #a0a0c0;
-  margin: 0.25rem 0 0;
+.logo-icon {
+    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+    color: #fff;
+    width: 40px;
+    height: 40px;
+    border-radius: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 1rem;
 }
 
-.header-right {
-  display: flex;
-  gap: 1rem;
+.nav-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 0.75rem 1rem;
+    color: #a0a0c0;
+    text-decoration: none;
+    border-radius: 8px;
+    margin-bottom: 0.5rem;
+    transition: all 0.2s;
 }
 
-.new-claim-btn {
-  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
-  color: #fff;
-  border: none;
-  padding: 0.75rem 1.5rem;
-  border-radius: 8px;
-  font-weight: 600;
-  cursor: pointer;
-  display: flex;
-  align-items: center;
-  gap: 0.5rem;
-  transition: transform 0.2s;
+.nav-item:hover, .nav-item.active {
+    background: rgba(79, 172, 254, 0.1);
+    color: #4facfe;
 }
 
-.new-claim-btn:hover {
-  transform: translateY(-2px);
+.user-profile {
+    margin-top: auto;
+    padding: 1rem;
+    background: rgba(255, 255, 255, 0.03);
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    gap: 12px;
 }
 
+.avatar {
+    width: 36px;
+    height: 36px;
+    background: #4facfe;
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-weight: bold;
+}
+
+.info {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+}
+
+.name { font-size: 0.85rem; font-weight: 600; }
+.dept { font-size: 0.7rem; color: #a0a0c0; }
+
 .logout-btn {
-  background: transparent;
-  color: #a0a0c0;
-  border: 1px solid rgba(160, 160, 192, 0.3);
-  padding: 0.75rem 1.5rem;
-  border-radius: 8px;
-  cursor: pointer;
+    background: transparent;
+    border: none;
+    color: #ff4d4f;
+    cursor: pointer;
+    padding: 4px;
 }
 
-.logout-btn:hover {
-  background: rgba(255, 255, 255, 0.05);
-  color: #fff;
+/* Content */
+.content {
+    flex: 1;
+    padding: 2rem 3rem;
+    overflow-y: auto;
 }
 
-.stats-grid {
-  display: grid;
-  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
-  gap: 1.5rem;
-  margin-bottom: 2rem;
+.content-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: flex-start;
+    margin-bottom: 2.5rem;
 }
 
-.stat-card {
-  background: rgba(255, 255, 255, 0.05);
-  padding: 1.5rem;
-  border-radius: 12px;
-  display: flex;
-  flex-direction: column;
-  gap: 0.5rem;
+h1 { font-size: 2rem; margin: 0; }
+.content-header p { color: #a0a0c0; margin: 5px 0 0; }
+
+.action-btn {
+    background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
+    color: #fff;
+    border: none;
+    padding: 0.75rem 1.5rem;
+    border-radius: 8px;
+    font-weight: 600;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    transition: transform 0.2s;
 }
 
-.stat-label {
-  color: #a0a0c0;
-  font-size: 0.9rem;
+.action-btn:hover { transform: translateY(-2px); }
+
+.stats-overview {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 1.5rem;
+    margin-bottom: 2.5rem;
 }
 
-.stat-value {
-  font-size: 1.5rem;
-  font-weight: bold;
+.stat-card {
+    background: rgba(255, 255, 255, 0.05);
+    padding: 1.5rem;
+    border-radius: 16px;
+    border: 1px solid rgba(255, 255, 255, 0.05);
 }
 
-.status-indicator {
-  width: 10px;
-  height: 10px;
-  border-radius: 50%;
-  display: inline-block;
+.stat-card .label { color: #a0a0c0; font-size: 0.85rem; }
+.stat-card .value { display: block; font-size: 1.75rem; font-weight: 700; margin-top: 8px; }
+
+/* Table */
+.claims-table-container {
+    background: rgba(255, 255, 255, 0.03);
+    border-radius: 16px;
+    border: 1px solid rgba(255, 255, 255, 0.05);
+    overflow: hidden;
+}
+
+.claims-table {
+    width: 100%;
+    border-collapse: collapse;
+    text-align: left;
 }
 
-.status-indicator.online {
-  background-color: #00e676;
-  box-shadow: 0 0 10px rgba(0, 230, 118, 0.5);
+.claims-table th {
+    padding: 1.25rem 1rem;
+    color: #a0a0c0;
+    font-weight: 500;
+    font-size: 0.85rem;
+    background: rgba(255, 255, 255, 0.02);
 }
 
-.table-container {
-  background: rgba(255, 255, 255, 0.03);
-  border-radius: 12px;
-  overflow: hidden;
-  position: relative;
-  min-height: 300px;
+.claims-table td {
+    padding: 1.25rem 1rem;
+    border-bottom: 1px solid rgba(255, 255, 255, 0.03);
 }
 
-table {
-  width: 100%;
-  border-collapse: collapse;
+.clickable-row { cursor: pointer; transition: background 0.2s; }
+.clickable-row:hover { background: rgba(255, 255, 255, 0.02); }
+
+.user-cell { display: flex; flex-direction: column; }
+.user-name { font-weight: 600; font-size: 0.9rem; }
+.user-dept { font-size: 0.75rem; color: #a0a0c0; }
+
+.capped-text { color: #ff4d4f; font-weight: 600; }
+
+.status-pill {
+    padding: 4px 10px;
+    border-radius: 20px;
+    font-size: 0.75rem;
+    font-weight: 600;
 }
 
-th {
-  text-align: left;
-  padding: 1.25rem 1rem;
-  color: #a0a0c0;
-  font-weight: 500;
-  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+.status-pill.verified { background: rgba(0, 230, 118, 0.1); color: #00e676; }
+.status-pill.review { background: rgba(255, 165, 0, 0.1); color: #ffa500; }
+
+/* Modal */
+.modal-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(0, 0, 0, 0.8);
+    backdrop-filter: blur(8px);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 1000;
 }
 
-td {
-  padding: 1.25rem 1rem;
-  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
-  vertical-align: middle;
+.modal-content {
+    background: #12121e;
+    width: 90%;
+    max-width: 800px;
+    border-radius: 24px;
+    padding: 2.5rem;
+    position: relative;
+    border: 1px solid rgba(255, 255, 255, 0.1);
 }
 
-.item-tag {
-  background: rgba(79, 172, 254, 0.1);
-  color: #4facfe;
-  padding: 0.2rem 0.6rem;
-  border-radius: 4px;
-  font-size: 0.8rem;
-  margin-right: 0.5rem;
+.close-btn {
+    position: absolute;
+    top: 1.5rem;
+    right: 1.5rem;
+    background: transparent;
+    border: none;
+    color: #a0a0c0;
+    cursor: pointer;
 }
 
-.more-items {
-  color: #a0a0c0;
-  font-size: 0.8rem;
+.audit-grid {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    gap: 2rem;
+    margin-top: 2rem;
 }
 
-.status-badge {
-  padding: 0.4rem 0.8rem;
-  border-radius: 20px;
-  font-size: 0.85rem;
-  font-weight: 500;
+.receipt-audit {
+    background: rgba(0, 0, 0, 0.3);
+    padding: 1.5rem;
+    border-radius: 12px;
 }
 
-.status-verified {
-  background: rgba(0, 230, 118, 0.1);
-  color: #00e676;
+.financial-audit {
+    background: rgba(255, 255, 255, 0.05);
+    padding: 1.5rem;
+    border-radius: 12px;
 }
 
-.status-review {
-  background: rgba(255, 171, 0, 0.1);
-  color: #ffab00;
+.audit-item {
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 1rem;
 }
 
-.loading-overlay {
-  position: absolute;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  background: rgba(18, 18, 30, 0.8);
-  z-index: 10;
+.audit-item.total {
+    margin-top: 1.5rem;
+    font-size: 1.25rem;
+    font-weight: bold;
+    color: #00e676;
 }
 
-.spinner {
-  width: 40px;
-  height: 40px;
-  border: 4px solid rgba(79, 172, 254, 0.1);
-  border-top-color: #4facfe;
-  border-radius: 50%;
-  animation: spin 1s linear infinite;
-  margin-bottom: 1rem;
+.ai-confidence-box {
+    margin-top: 2rem;
 }
 
-@keyframes spin {
-  to { transform: rotate(360deg); }
+.conf-bar {
+    height: 6px;
+    background: rgba(255, 255, 255, 0.1);
+    border-radius: 3px;
+    margin: 10px 0;
 }
 
-.empty-state {
-  text-align: center;
-  padding: 4rem;
-  color: #a0a0c0;
+.conf-fill {
+    height: 100%;
+    background: #4facfe;
+    border-radius: 3px;
 }
+
+.icon-tiny { width: 14px; height: 14px; }
+.icon-small { width: 20px; height: 20px; }

+ 121 - 42
ai-data-entry-ui/src/app/components/claims-dashboard/claims-dashboard.component.html

@@ -1,70 +1,149 @@
-<div class="dashboard-container">
-  <header>
-    <div class="header-left">
-      <h1>Claims Dashboard</h1>
-      <p>Welcome back, <strong>{{ currentUser?.name }}</strong> ({{ currentUser?.department }})</p>
+<div class="dashboard-wrapper">
+  <!-- Sidebar -->
+  <aside class="sidebar">
+    <div class="logo">
+      <div class="logo-icon">AI</div>
+      <span>SmartClaims</span>
     </div>
-    <div class="header-right">
-      <button class="new-claim-btn" routerLink="/extract">
-        <span class="plus-icon">+</span> New Claim
+    
+    <nav>
+      <a routerLink="/dashboard" class="nav-item active">Dashboard</a>
+      <a routerLink="/user-management" class="nav-item">
+        <lucide-icon [name]="UserCog" class="icon-small"></lucide-icon> Users
+      </a>
+    </nav>
+
+    <div class="user-profile" *ngIf="currentUser">
+      <div class="avatar">{{ currentUser.name[0] }}</div>
+      <div class="info">
+        <span class="name">{{ currentUser.name }}</span>
+        <span class="dept">{{ currentUser.department }}</span>
+      </div>
+      <button class="logout-btn" (click)="logout()" title="Logout">
+        <lucide-icon [name]="LogOut" class="icon-small"></lucide-icon>
       </button>
-      <button class="logout-btn" (click)="logout()">Logout</button>
     </div>
-  </header>
+  </aside>
+
+  <!-- Main Content -->
+  <main class="content">
+    <header class="content-header">
+      <div>
+        <h1>Claims Intelligence</h1>
+        <p>Monitor and audit employee medical expenditure</p>
+      </div>
+      <button class="action-btn" routerLink="/extract">
+        <lucide-icon [name]="Plus" class="icon-small"></lucide-icon> New Claim
+      </button>
+    </header>
 
-  <main>
-    <div class="stats-grid">
+    <div class="stats-overview">
       <div class="stat-card">
-        <span class="stat-label">Total Submissions</span>
-        <span class="stat-value">{{ claims.length }}</span>
+        <span class="label">Total Claims</span>
+        <span class="value">{{ claims.length }}</span>
       </div>
       <div class="stat-card">
-        <span class="stat-label">System Health</span>
-        <span class="status-indicator online"></span>
-        <span class="stat-value">Online</span>
+        <span class="label">Total Claimed</span>
+        <span class="value">RM {{ totalClaimed | number:'1.2-2' }}</span>
       </div>
     </div>
 
-    <div class="table-container">
-      <div *ngIf="loading" class="loading-overlay">
-        <div class="spinner"></div>
-        <p>Fetching claims...</p>
-      </div>
-
-      <table *ngIf="!loading">
+    <div class="claims-table-container">
+      <table class="claims-table">
         <thead>
           <tr>
-            <th>Date</th>
+            <th>Employee</th>
             <th>Provider</th>
-            <th>Amount</th>
-            <th>Items</th>
-            <th>AI Status</th>
+            <th>Date</th>
+            <th>Spent (AI)</th>
+            <th>Claimed (Capped)</th>
+            <th>Status</th>
+            <th>Audit</th>
           </tr>
         </thead>
         <tbody>
-          <tr *ngFor="let claim of claims">
-            <td>{{ claim.timestamp | date:'mediumDate' }}</td>
+          <tr *ngFor="let claim of claims" (click)="viewDetail(claim)" class="clickable-row">
+            <td>
+              <div class="user-cell">
+                <span class="user-name">{{ claim.submitted_by }}</span>
+                <span class="user-dept">{{ claim.department }}</span>
+              </div>
+            </td>
             <td>{{ claim.extraction_data.provider_name }}</td>
-            <td><strong>{{ claim.extraction_data.currency }} {{ claim.extraction_data.total_amount | number:'1.2-2' }}</strong></td>
+            <td>{{ 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="item-tag" *ngFor="let item of claim.extraction_data.items.slice(0, 2)">
-                {{ item }}
-              </span>
-              <span *ngIf="claim.extraction_data.items.length > 2" class="more-items">
-                +{{ claim.extraction_data.items.length - 2 }}
+              <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>
             </td>
             <td>
-              <span class="status-badge" [ngClass]="claim.extraction_data.needs_manual_review ? 'status-review' : 'status-verified'">
-                {{ claim.extraction_data.needs_manual_review ? 'Review Required' : 'Verified' }}
-              </span>
+               <lucide-icon [name]="ExternalLink" class="icon-tiny"></lucide-icon>
             </td>
           </tr>
-          <tr *ngIf="claims.length === 0">
-            <td colspan="5" class="empty-state">No claims found. Start by creating a new one!</td>
-          </tr>
         </tbody>
       </table>
+      <div *ngIf="loading" class="table-loading">Syncing secure data...</div>
+      <div *ngIf="!loading && claims.length === 0" class="empty-table">No claims recorded in this session.</div>
     </div>
   </main>
+
+  <!-- Detail Modal -->
+  <div class="modal-overlay" *ngIf="selectedClaim" (click)="closeDetail()">
+    <div class="modal-content" (click)="$event.stopPropagation()">
+      <button class="close-btn" (click)="closeDetail()">
+        <lucide-icon [name]="X" class="icon-small"></lucide-icon>
+      </button>
+      
+      <div class="modal-header">
+        <h2>Claim Audit Details</h2>
+        <span class="claim-id">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="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>
+            </div>
+          </div>
+
+          <div class="financial-audit">
+            <h3>Policy Enforcement</h3>
+            <div class="audit-item">
+              <span class="label">Gross Spent:</span>
+              <span class="val">RM {{ selectedClaim.amount_spent | number:'1.2-2' }}</span>
+            </div>
+            <div class="audit-item">
+              <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 total">
+              <span class="label">Total Paid:</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>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
 </div>

+ 19 - 1
ai-data-entry-ui/src/app/components/claims-dashboard/claims-dashboard.component.ts

@@ -4,11 +4,12 @@ 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';
 
 @Component({
   selector: 'app-dashboard',
   standalone: true,
-  imports: [CommonModule, RouterModule],
+  imports: [CommonModule, RouterModule, LucideAngularModule],
   templateUrl: './claims-dashboard.component.html',
   styleUrls: ['./claims-dashboard.component.css']
 })
@@ -16,6 +17,14 @@ export class DashboardComponent implements OnInit {
   claims: ClaimRecord[] = [];
   currentUser: User | null = null;
   loading = true;
+  selectedClaim: ClaimRecord | null = null;
+  totalClaimed = 0;
+
+  readonly LogOut = LogOut;
+  readonly Plus = Plus;
+  readonly UserCog = UserCog;
+  readonly ExternalLink = ExternalLink;
+  readonly X = X;
 
   constructor(
     private extractionService: ExtractionService,
@@ -37,6 +46,7 @@ export class DashboardComponent implements OnInit {
     this.extractionService.getClaims().subscribe({
       next: (data) => {
         this.claims = data.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
+        this.totalClaimed = this.claims.reduce((sum, c) => sum + c.amount_claimed, 0);
         this.loading = false;
       },
       error: (err) => {
@@ -46,6 +56,14 @@ export class DashboardComponent implements OnInit {
     });
   }
 
+  viewDetail(claim: ClaimRecord): void {
+    this.selectedClaim = claim;
+  }
+
+  closeDetail(): void {
+    this.selectedClaim = null;
+  }
+
   logout(): void {
     this.sessionService.logout();
     this.router.navigate(['/']);

+ 165 - 0
ai-data-entry-ui/src/app/components/user-management/user-management.component.css

@@ -0,0 +1,165 @@
+.management-container {
+  padding: 2rem;
+  max-width: 1200px;
+  margin: 0 auto;
+  font-family: 'Inter', sans-serif;
+  color: #fff;
+  background-color: #12121e;
+  min-height: 100vh;
+}
+
+header {
+  margin-bottom: 2rem;
+}
+
+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;
+}
+
+header p {
+  color: #a0a0c0;
+}
+
+.management-grid {
+  display: grid;
+  grid-template-columns: 1fr 2fr;
+  gap: 2rem;
+}
+
+.card {
+  background: rgba(255, 255, 255, 0.05);
+  border-radius: 16px;
+  padding: 2rem;
+  border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+h2 {
+  font-size: 1.25rem;
+  margin-bottom: 1.5rem;
+  color: #fff;
+}
+
+.form-field {
+  margin-bottom: 1.5rem;
+}
+
+.form-field label {
+  display: block;
+  margin-bottom: 0.5rem;
+  color: #a0a0c0;
+  font-size: 0.9rem;
+}
+
+input {
+  width: 100%;
+  padding: 0.75rem;
+  background: rgba(0, 0, 0, 0.2);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 8px;
+  color: #fff;
+}
+
+.submit-btn {
+  width: 100%;
+  background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
+  border: none;
+  padding: 1rem;
+  border-radius: 8px;
+  font-weight: 600;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 0.5rem;
+}
+
+.user-list {
+  display: flex;
+  flex-direction: column;
+  gap: 1rem;
+}
+
+.user-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  background: rgba(255, 255, 255, 0.03);
+  padding: 1rem;
+  border-radius: 12px;
+  transition: background 0.2s;
+}
+
+.user-item:hover {
+  background: rgba(255, 255, 255, 0.07);
+}
+
+.user-info {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+
+.user-avatar-small {
+  width: 40px;
+  height: 40px;
+  background: #4facfe;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-weight: bold;
+}
+
+.user-info h3 {
+  margin: 0;
+  font-size: 1rem;
+}
+
+.user-info p {
+  margin: 0;
+  font-size: 0.8rem;
+  color: #a0a0c0;
+}
+
+.user-balance {
+  text-align: right;
+  display: flex;
+  flex-direction: column;
+}
+
+.user-balance .label {
+  font-size: 0.75rem;
+  color: #a0a0c0;
+}
+
+.user-balance .value {
+  font-weight: bold;
+  color: #00e676;
+}
+
+.back-btn {
+  background: transparent;
+  border: none;
+  color: #4facfe;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  padding: 0;
+}
+
+.icon-small {
+  width: 18px;
+  height: 18px;
+}
+
+@media (max-width: 900px) {
+  .management-grid {
+    grid-template-columns: 1fr;
+  }
+}

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

@@ -0,0 +1,56 @@
+<div class="management-container">
+  <header>
+    <button class="back-btn" routerLink="/dashboard">
+      <lucide-icon [name]="ArrowLeft" class="icon-small"></lucide-icon> Back to Dashboard
+    </button>
+    <h1>User Management</h1>
+    <p>Manage employee medical allowances and profiles</p>
+  </header>
+
+  <div class="management-grid">
+    <!-- Creation Form -->
+    <div class="card form-card">
+      <h2>Add New Employee</h2>
+      <form [formGroup]="userForm" (ngSubmit)="onSubmit()">
+        <div class="form-field">
+          <label for="name">Full Name</label>
+          <input id="name" type="text" formControlName="name" placeholder="E.g. John Doe">
+        </div>
+        <div class="form-field">
+          <label for="department">Department</label>
+          <input id="department" type="text" formControlName="department" placeholder="E.g. Engineering">
+        </div>
+        <div class="form-field">
+          <label for="medical_allowance">Annual Allowance (MYR)</label>
+          <input id="medical_allowance" type="number" formControlName="medical_allowance">
+        </div>
+        <button type="submit" [disabled]="!userForm.valid || isLoading" class="submit-btn text-white">
+          <lucide-icon [name]="UserPlus" class="icon-small"></lucide-icon>
+          {{ isLoading ? 'Creating...' : 'Create Profile' }}
+        </button>
+      </form>
+    </div>
+
+    <!-- User List -->
+    <div class="card list-card">
+      <h2>Employee Directory</h2>
+      <div class="user-list">
+        <div *ngIf="isLoading && users.length === 0" class="loading">Loading directory...</div>
+        <div *ngFor="let user of users" class="user-item">
+          <div class="user-info">
+            <div class="user-avatar-small">{{ user.name[0] }}</div>
+            <div>
+              <h3>{{ user.name }}</h3>
+              <p>{{ user.department }}</p>
+            </div>
+          </div>
+          <div class="user-balance">
+            <span class="label">Balance</span>
+            <span class="value">RM {{ user.medical_allowance | number:'1.2-2' }}</span>
+          </div>
+        </div>
+        <div *ngIf="!isLoading && users.length === 0" class="empty-state">No employees registered yet.</div>
+      </div>
+    </div>
+  </div>
+</div>

+ 75 - 0
ai-data-entry-ui/src/app/components/user-management/user-management.component.ts

@@ -0,0 +1,75 @@
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
+import { Router, RouterModule } from '@angular/router';
+import { ExtractionService } from '../../services/extraction.service';
+import { UserAccount } from '../../services/extraction';
+import { LucideAngularModule, UserPlus, ArrowLeft, Trash2 } from 'lucide-angular';
+
+@Component({
+  selector: 'app-user-management',
+  standalone: true,
+  imports: [CommonModule, ReactiveFormsModule, RouterModule, LucideAngularModule],
+  templateUrl: './user-management.component.html',
+  styleUrls: ['./user-management.component.css']
+})
+export class UserManagementComponent implements OnInit {
+  userForm: FormGroup;
+  users: UserAccount[] = [];
+  isLoading = false;
+  readonly UserPlus = UserPlus;
+  readonly ArrowLeft = ArrowLeft;
+  readonly Trash2 = Trash2;
+
+  constructor(
+    private fb: FormBuilder,
+    private extractionService: ExtractionService,
+    private router: Router
+  ) {
+    this.userForm = this.fb.group({
+      name: ['', Validators.required],
+      department: ['', Validators.required],
+      medical_allowance: [1000, [Validators.required, Validators.min(0)]]
+    });
+  }
+
+  ngOnInit(): void {
+    this.loadUsers();
+  }
+
+  loadUsers(): void {
+    this.isLoading = true;
+    this.extractionService.getUsers().subscribe({
+      next: (data) => {
+        this.users = data;
+        this.isLoading = false;
+      },
+      error: (err) => {
+        console.error('Error loading users:', err);
+        this.isLoading = false;
+      }
+    });
+  }
+
+  onSubmit(): void {
+    if (this.userForm.invalid) return;
+
+    this.isLoading = true;
+    const newUser: UserAccount = {
+      id: Math.random().toString(36).substr(2, 9),
+      ...this.userForm.value
+    };
+
+    this.extractionService.createUser(newUser).subscribe({
+      next: () => {
+        this.loadUsers();
+        this.userForm.reset({ medical_allowance: 1000 });
+        this.isLoading = false;
+      },
+      error: (err) => {
+        console.error('Error creating user:', err);
+        this.isLoading = false;
+      }
+    });
+  }
+}

+ 22 - 9
ai-data-entry-ui/src/app/components/user-selection/user-selection.component.html

@@ -1,14 +1,27 @@
 <div class="selection-container">
-  <h1>Welcome to AI Claims Portal</h1>
-  <p>Please select your profile to continue</p>
-  
-  <div class="user-grid">
-    <div *ngFor="let user of users" class="user-card" (click)="selectUser(user)">
-      <div class="user-avatar">{{ user.name[0] }}</div>
-      <div class="user-info">
-        <h3>{{ user.name }}</h3>
-        <p>{{ user.department }}</p>
+  <div class="glass-card">
+    <header>
+      <h1>Internal Claims Portal</h1>
+      <p>Select your profile to continue to the intelligence dashboard</p>
+    </header>
+
+    <div *ngIf="isLoading" class="loading-state">
+       <div class="spinner"></div>
+       <p>Fetching secure profiles...</p>
+    </div>
+
+    <div *ngIf="!isLoading" class="user-grid">
+      <div *ngFor="let user of users" class="user-card" (click)="selectUser(user)">
+        <div class="avatar">{{ user.name[0] }}</div>
+        <div class="info">
+          <span class="name">{{ user.name }}</span>
+          <span class="dept">{{ user.department }}</span>
+        </div>
       </div>
     </div>
+    
+    <div *ngIf="!isLoading && users.length === 0" class="empty-state">
+      <p>No profiles found. Database initialization required.</p>
+    </div>
   </div>
 </div>

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

@@ -1,7 +1,9 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { Router } from '@angular/router';
 import { SessionService, User } from '../../services/session.service';
+import { ExtractionService } from '../../services/extraction.service';
+import { UserAccount } from '../../services/extraction';
 
 @Component({
   selector: 'app-user-selection',
@@ -10,17 +12,42 @@ import { SessionService, User } from '../../services/session.service';
   templateUrl: './user-selection.component.html',
   styleUrls: ['./user-selection.component.css']
 })
-export class UserSelectionComponent {
-  users: User[] = [
-    { name: 'Ahmad', department: 'Engineering' },
-    { name: 'Siti', department: 'Finance' },
-    { name: 'Tan', department: 'Operations' }
-  ];
+export class UserSelectionComponent implements OnInit {
+  users: UserAccount[] = [];
+  isLoading = true;
 
-  constructor(private sessionService: SessionService, private router: Router) {}
+  constructor(
+    private sessionService: SessionService, 
+    private extractionService: ExtractionService,
+    private router: Router
+  ) {}
 
-  selectUser(user: User): void {
-    this.sessionService.setCurrentUser(user);
+  ngOnInit(): void {
+    this.loadUsers();
+  }
+
+  loadUsers(): void {
+    this.isLoading = true;
+    this.extractionService.getUsers().subscribe({
+      next: (data) => {
+        this.users = data;
+        this.isLoading = false;
+      },
+      error: (err) => {
+        console.error('Error loading users:', err);
+        this.isLoading = false;
+      }
+    });
+  }
+
+  selectUser(user: UserAccount): void {
+    const sessionUser: User = {
+      id: user.id,
+      name: user.name,
+      department: user.department,
+      medical_allowance: user.medical_allowance
+    };
+    this.sessionService.setCurrentUser(sessionUser);
     this.router.navigate(['/dashboard']);
   }
 }

+ 17 - 4
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 } from './extraction';
+import { ExtractionResponse, ClaimRecord, UserAccount } from './extraction';
 
 @Injectable({
   providedIn: 'root'
@@ -11,6 +11,20 @@ export class ExtractionService {
 
   constructor(private http: HttpClient) { }
 
+  getUsers(): Observable<UserAccount[]> {
+    return this.http.get<UserAccount[]>(`${this.apiUrl}/users`)
+      .pipe(
+        catchError(this.handleError)
+      );
+  }
+
+  createUser(user: UserAccount): Observable<UserAccount> {
+    return this.http.post<UserAccount>(`${this.apiUrl}/users`, user)
+      .pipe(
+        catchError(this.handleError)
+      );
+  }
+
   extractData(file: File, userName: string = 'Demo User', department: string = 'R&D'): Observable<ExtractionResponse> {
     const formData = new FormData();
     formData.append('file', file);
@@ -23,10 +37,9 @@ export class ExtractionService {
       );
   }
 
-  submitClaim(extractionData: ExtractionResponse, userName: string, department: string): Observable<ClaimRecord> {
+  submitClaim(extractionData: ExtractionResponse, userId: string): Observable<ClaimRecord> {
     const headers = new HttpHeaders({
-      'user-name': userName || 'Unknown',
-      'department': department || 'Unknown'
+      'user-id': userId
     });
 
     return this.http.post<ClaimRecord>(`${this.apiUrl}/claims`, extractionData, { headers })

+ 10 - 0
ai-data-entry-ui/src/app/services/extraction.ts

@@ -1,3 +1,10 @@
+export interface UserAccount {
+  id: string;
+  name: string;
+  department: string;
+  medical_allowance: number;
+}
+
 export interface ExtractionResponse {
   provider_name: string;
   visit_date: string;
@@ -6,6 +13,7 @@ export interface ExtractionResponse {
   items: string[];
   confidence_score: number;
   needs_manual_review: boolean;
+  ai_reasoning: string;
 }
 
 export interface ClaimRecord {
@@ -13,5 +21,7 @@ export interface ClaimRecord {
   timestamp: string;
   submitted_by: string;
   department: string;
+  amount_spent: number;
+  amount_claimed: number;
   extraction_data: ExtractionResponse;
 }

+ 2 - 0
ai-data-entry-ui/src/app/services/session.service.ts

@@ -2,8 +2,10 @@ import { Injectable } from '@angular/core';
 import { BehaviorSubject, Observable } from 'rxjs';
 
 export interface User {
+  id: string;
   name: string;
   department: string;
+  medical_allowance: number;
 }
 
 @Injectable({

+ 31 - 6
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
+from backend.schemas import ExtractionResponse, ClaimRecord, UserAccount
 from dotenv import load_dotenv
 
 load_dotenv()
@@ -21,13 +21,23 @@ app.add_middleware(
     allow_headers=["*"],
 )
 
-# In-memory database for claims
+# In-memory database
 CLAIMS_DB: List[ClaimRecord] = []
+USERS_DB: List[UserAccount] = []
 
 @app.get("/health")
 async def health_check():
     return {"status": "healthy"}
 
+@app.get("/api/v1/users", response_model=List[UserAccount])
+async def get_users():
+    return USERS_DB
+
+@app.post("/api/v1/users", response_model=UserAccount)
+async def create_user(user: UserAccount):
+    USERS_DB.append(user)
+    return user
+
 @app.post("/api/v1/extract", response_model=ExtractionResponse)
 async def extract_receipt(
     file: UploadFile = File(...),
@@ -60,14 +70,29 @@ async def extract_receipt(
 @app.post("/api/v1/claims", response_model=ClaimRecord)
 async def submit_claim(
     extraction_data: ExtractionResponse,
-    user_name: str = Header(...),
-    department: str = Header(...)
+    user_id: str = Header(...)
 ):
+    # 1. Find User
+    user = next((u for u in USERS_DB if u.id == user_id), None)
+    if not user:
+        raise HTTPException(status_code=404, detail="User not found")
+
+    # 2. Financial Guard Logic
+    spent_amount = extraction_data.total_amount
+    remaining = user.medical_allowance
+    amount_claimed = min(spent_amount, remaining)
+
+    # 3. Update User Balance
+    user.medical_allowance -= amount_claimed
+
+    # 4. Save Claim
     claim = ClaimRecord(
         id=str(uuid.uuid4()),
         timestamp=datetime.now().isoformat(),
-        submitted_by=user_name,
-        department=department,
+        submitted_by=user.name,
+        department=user.department,
+        amount_spent=spent_amount,
+        amount_claimed=amount_claimed,
         extraction_data=extraction_data
     )
     CLAIMS_DB.append(claim)

+ 9 - 0
backend/schemas.py

@@ -3,6 +3,12 @@ from typing import List, Optional
 from uuid import UUID
 from datetime import datetime
 
+class UserAccount(BaseModel):
+    id: str = Field(description="Unique identifier for the user.")
+    name: str = Field(description="Full name of the employee.")
+    department: str = Field(description="Department name.")
+    medical_allowance: float = Field(description="Remaining annual medical allowance in MYR.")
+
 class ExtractionResponse(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.")
@@ -11,10 +17,13 @@ class ExtractionResponse(BaseModel):
     items: List[str] = Field(description="Simplified list of services (e.g. 'Consultation', 'Medicine').")
     confidence_score: float = Field(description="Model's confidence from 0.0 to 1.0.")
     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 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_claimed: float = Field(description="The actual amount credited after policy capping.")
     extraction_data: ExtractionResponse = Field(description="The AI-extracted data for this claim.")

+ 1 - 0
backend/services/openai_service.py

@@ -51,6 +51,7 @@ async def extract_receipt_data(image_content: bytes, user_name: str, department:
         f"2. The receipt looks hand-written and lacks an official stamp. "
         f"3. The provider name is missing or the amount looks altered. "
         f"4. The user's name ({user_name}) is not clearly visible on the receipt. "
+        f"IMPORTANT: Fill the `ai_reasoning` field with a 2-sentence explanation of how you identified the clinic and the total amount. "
         f"If the document is a Tax Invoice, extract the Invoice Number and add it to the `items` list."
     )