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