claim-form.component.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { Component, OnInit, OnDestroy } from '@angular/core';
  2. import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
  3. import { CommonModule } from '@angular/common';
  4. import { Router, RouterModule } from '@angular/router';
  5. import { ExtractionService } from '../../services/extraction.service';
  6. import { SessionService, User } from '../../services/session.service';
  7. import { ExtractionResponse, ClaimSubmission } from '../../services/extraction';
  8. import { LucideAngularModule, ShieldCheck, AlertCircle, CheckCircle, ArrowLeft, Eye, EyeOff, Wand2 } from 'lucide-angular';
  9. import { Subscription } from 'rxjs';
  10. @Component({
  11. selector: 'app-claim-form',
  12. standalone: true,
  13. imports: [CommonModule, ReactiveFormsModule, LucideAngularModule, RouterModule],
  14. templateUrl: './claim-form.component.html',
  15. styleUrls: ['./claim-form.component.css']
  16. })
  17. export class ClaimFormComponent implements OnInit, OnDestroy {
  18. claimForm: FormGroup;
  19. currentUser: User | null = null;
  20. previewUrl: string | null = null;
  21. selectedFile: File | null = null;
  22. extractionResponse: ExtractionResponse | null = null;
  23. isLoading = false;
  24. isSubmitting = false;
  25. isDragging = false;
  26. showRawJson = false;
  27. private amountSpentSub: Subscription | null = null;
  28. readonly ShieldCheck = ShieldCheck;
  29. readonly AlertCircle = AlertCircle;
  30. readonly CheckCircle = CheckCircle;
  31. readonly ArrowLeft = ArrowLeft;
  32. readonly Eye = Eye;
  33. readonly EyeOff = EyeOff;
  34. readonly Wand2 = Wand2;
  35. errorMessage: string | null = null;
  36. constructor(
  37. private fb: FormBuilder,
  38. private extractionService: ExtractionService,
  39. private sessionService: SessionService,
  40. private router: Router
  41. ) {
  42. this.claimForm = this.fb.group({
  43. provider_name: ['', Validators.required],
  44. visit_date: ['', Validators.required],
  45. amount_spent: ['', [Validators.required, Validators.min(0.01)]],
  46. amount_claimed: [{ value: 0, disabled: true }],
  47. currency: ['MYR']
  48. });
  49. }
  50. ngOnInit(): void {
  51. this.currentUser = this.sessionService.getCurrentUser();
  52. if (!this.currentUser) {
  53. this.router.navigate(['/']);
  54. return;
  55. }
  56. this.amountSpentSub = this.claimForm.get('amount_spent')?.valueChanges.subscribe(() => {
  57. this.calculateClaimable();
  58. }) || null;
  59. }
  60. ngOnDestroy(): void {
  61. if (this.amountSpentSub) {
  62. this.amountSpentSub.unsubscribe();
  63. }
  64. }
  65. calculateClaimable(): void {
  66. if (!this.currentUser) return;
  67. const spent = Number(this.claimForm.get('amount_spent')?.value) || 0;
  68. const remaining = this.currentUser.medical_allowance;
  69. const claimable = Math.min(spent, remaining);
  70. this.claimForm.patchValue({ amount_claimed: claimable }, { emitEvent: false });
  71. }
  72. onDragOver(event: DragEvent): void {
  73. event.preventDefault();
  74. event.stopPropagation();
  75. this.isDragging = true;
  76. }
  77. onDragLeave(event: DragEvent): void {
  78. event.preventDefault();
  79. event.stopPropagation();
  80. this.isDragging = false;
  81. }
  82. onDrop(event: DragEvent): void {
  83. event.preventDefault();
  84. event.stopPropagation();
  85. this.isDragging = false;
  86. const files = event.dataTransfer?.files;
  87. if (files && files.length > 0) {
  88. this.handleFileSelection(files[0]);
  89. }
  90. }
  91. onFileSelected(event: any): void {
  92. const file: File = event.target.files[0];
  93. if (file) {
  94. this.handleFileSelection(file);
  95. }
  96. }
  97. private handleFileSelection(file: File): void {
  98. if (!this.currentUser) return;
  99. this.selectedFile = file;
  100. this.previewUrl = URL.createObjectURL(file);
  101. this.extractionResponse = null; // reset if existing
  102. this.errorMessage = null;
  103. }
  104. autoFillWithAI(): void {
  105. if (!this.currentUser || !this.selectedFile) return;
  106. this.isLoading = true;
  107. this.errorMessage = null;
  108. this.extractionService.extractData(this.selectedFile, this.currentUser.name, this.currentUser.department).subscribe({
  109. next: (response) => {
  110. this.extractionResponse = response;
  111. this.claimForm.patchValue({
  112. provider_name: response.provider_name,
  113. visit_date: response.visit_date,
  114. amount_spent: response.total_amount,
  115. currency: response.currency || 'MYR'
  116. });
  117. // The valueChanges subscription will trigger calculateClaimable automatically
  118. this.isLoading = false;
  119. },
  120. error: (err: Error) => {
  121. console.error('Extraction failed', err);
  122. this.errorMessage = err.message;
  123. this.isLoading = false;
  124. }
  125. });
  126. }
  127. onSubmit(): void {
  128. if (this.claimForm.invalid || !this.currentUser) return;
  129. this.isSubmitting = true;
  130. const formData = this.claimForm.getRawValue();
  131. const submissionData: ClaimSubmission = {
  132. provider_name: formData.provider_name,
  133. visit_date: formData.visit_date,
  134. amount_spent: Number(formData.amount_spent),
  135. currency: formData.currency,
  136. extraction_data: this.extractionResponse
  137. };
  138. this.extractionService.submitClaim(submissionData, this.currentUser.id).subscribe({
  139. next: () => {
  140. this.isSubmitting = false;
  141. this.router.navigate(['/']);
  142. },
  143. error: (err) => {
  144. console.error('Submission failed', err);
  145. this.errorMessage = 'Failed to submit claim. Please try again.';
  146. this.isSubmitting = false;
  147. }
  148. });
  149. }
  150. get rawJson(): string {
  151. return JSON.stringify(this.extractionResponse, null, 2);
  152. }
  153. }