rairo commited on
Commit
54615dc
·
verified ·
1 Parent(s): 417d6c4

Upload receipt_processing_ui.html

Browse files
Files changed (1) hide show
  1. receipt_processing_ui.html +785 -0
receipt_processing_ui.html ADDED
@@ -0,0 +1,785 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Qx Intelligent Receipt Processing</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ color: #333;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1200px;
23
+ margin: 0 auto;
24
+ padding: 20px;
25
+ }
26
+
27
+ .header {
28
+ text-align: center;
29
+ margin-bottom: 40px;
30
+ color: white;
31
+ }
32
+
33
+ .header h1 {
34
+ font-size: 2.5rem;
35
+ margin-bottom: 10px;
36
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
37
+ }
38
+
39
+ .header p {
40
+ font-size: 1.1rem;
41
+ opacity: 0.9;
42
+ }
43
+
44
+ .main-content {
45
+ background: white;
46
+ border-radius: 20px;
47
+ box-shadow: 0 20px 40px rgba(0,0,0,0.1);
48
+ overflow: hidden;
49
+ }
50
+
51
+ .tabs {
52
+ display: flex;
53
+ background: #f8f9fa;
54
+ border-bottom: 2px solid #e9ecef;
55
+ }
56
+
57
+ .tab {
58
+ flex: 1;
59
+ padding: 20px;
60
+ text-align: center;
61
+ background: none;
62
+ border: none;
63
+ cursor: pointer;
64
+ font-size: 1rem;
65
+ font-weight: 600;
66
+ color: #6c757d;
67
+ transition: all 0.3s ease;
68
+ }
69
+
70
+ .tab.active {
71
+ background: #4285f4;
72
+ color: white;
73
+ }
74
+
75
+ .tab:hover:not(.active) {
76
+ background: #e3f2fd;
77
+ color: #1976d2;
78
+ }
79
+
80
+ .tab-content {
81
+ padding: 40px;
82
+ display: none;
83
+ }
84
+
85
+ .tab-content.active {
86
+ display: block;
87
+ }
88
+
89
+ .upload-area {
90
+ border: 3px dashed #4285f4;
91
+ border-radius: 15px;
92
+ padding: 40px;
93
+ text-align: center;
94
+ margin-bottom: 30px;
95
+ cursor: pointer;
96
+ transition: all 0.3s ease;
97
+ background: #f8f9ff;
98
+ }
99
+
100
+ .upload-area:hover {
101
+ border-color: #1976d2;
102
+ background: #e3f2fd;
103
+ }
104
+
105
+ .upload-area.dragover {
106
+ border-color: #1976d2;
107
+ background: #e3f2fd;
108
+ transform: scale(1.02);
109
+ }
110
+
111
+ .upload-icon {
112
+ font-size: 3rem;
113
+ color: #4285f4;
114
+ margin-bottom: 20px;
115
+ }
116
+
117
+ .btn {
118
+ background: #4285f4;
119
+ color: white;
120
+ border: none;
121
+ padding: 12px 30px;
122
+ border-radius: 25px;
123
+ cursor: pointer;
124
+ font-size: 1rem;
125
+ font-weight: 600;
126
+ transition: all 0.3s ease;
127
+ margin: 10px;
128
+ }
129
+
130
+ .btn:hover {
131
+ background: #1976d2;
132
+ transform: translateY(-2px);
133
+ box-shadow: 0 5px 15px rgba(66, 133, 244, 0.3);
134
+ }
135
+
136
+ .btn:disabled {
137
+ background: #ccc;
138
+ cursor: not-allowed;
139
+ transform: none;
140
+ box-shadow: none;
141
+ }
142
+
143
+ .btn-secondary {
144
+ background: #6c757d;
145
+ }
146
+
147
+ .btn-secondary:hover {
148
+ background: #545b62;
149
+ }
150
+
151
+ .btn-success {
152
+ background: #28a745;
153
+ }
154
+
155
+ .btn-success:hover {
156
+ background: #218838;
157
+ }
158
+
159
+ .loading {
160
+ display: none;
161
+ text-align: center;
162
+ padding: 20px;
163
+ }
164
+
165
+ .spinner {
166
+ width: 40px;
167
+ height: 40px;
168
+ border: 4px solid #f3f3f3;
169
+ border-top: 4px solid #4285f4;
170
+ border-radius: 50%;
171
+ animation: spin 1s linear infinite;
172
+ margin: 0 auto 15px;
173
+ }
174
+
175
+ @keyframes spin {
176
+ 0% { transform: rotate(0deg); }
177
+ 100% { transform: rotate(360deg); }
178
+ }
179
+
180
+ .results {
181
+ margin-top: 30px;
182
+ }
183
+
184
+ .receipt-card {
185
+ background: #f8f9fa;
186
+ border-radius: 10px;
187
+ padding: 20px;
188
+ margin-bottom: 20px;
189
+ border-left: 5px solid #4285f4;
190
+ }
191
+
192
+ .receipt-header {
193
+ display: flex;
194
+ justify-content: between;
195
+ align-items: center;
196
+ margin-bottom: 15px;
197
+ flex-wrap: wrap;
198
+ gap: 10px;
199
+ }
200
+
201
+ .store-name {
202
+ font-size: 1.2rem;
203
+ font-weight: bold;
204
+ color: #1976d2;
205
+ }
206
+
207
+ .receipt-date {
208
+ color: #6c757d;
209
+ font-size: 0.9rem;
210
+ }
211
+
212
+ .total-amount {
213
+ font-size: 1.1rem;
214
+ font-weight: bold;
215
+ color: #28a745;
216
+ }
217
+
218
+ .items-table {
219
+ width: 100%;
220
+ border-collapse: collapse;
221
+ margin-top: 15px;
222
+ }
223
+
224
+ .items-table th,
225
+ .items-table td {
226
+ padding: 10px;
227
+ text-align: left;
228
+ border-bottom: 1px solid #dee2e6;
229
+ }
230
+
231
+ .items-table th {
232
+ background: #e9ecef;
233
+ font-weight: 600;
234
+ color: #495057;
235
+ }
236
+
237
+ .category-stock {
238
+ background: #d4edda;
239
+ color: #155724;
240
+ padding: 3px 8px;
241
+ border-radius: 12px;
242
+ font-size: 0.8rem;
243
+ font-weight: 600;
244
+ }
245
+
246
+ .category-expense {
247
+ background: #f8d7da;
248
+ color: #721c24;
249
+ padding: 3px 8px;
250
+ border-radius: 12px;
251
+ font-size: 0.8rem;
252
+ font-weight: 600;
253
+ }
254
+
255
+ .error {
256
+ background: #f8d7da;
257
+ color: #721c24;
258
+ padding: 15px;
259
+ border-radius: 5px;
260
+ margin-top: 15px;
261
+ }
262
+
263
+ .success {
264
+ background: #d4edda;
265
+ color: #155724;
266
+ padding: 15px;
267
+ border-radius: 5px;
268
+ margin-top: 15px;
269
+ }
270
+
271
+ .file-list {
272
+ margin-top: 20px;
273
+ }
274
+
275
+ .file-item {
276
+ display: flex;
277
+ justify-content: space-between;
278
+ align-items: center;
279
+ padding: 10px;
280
+ background: #f8f9fa;
281
+ border-radius: 5px;
282
+ margin-bottom: 10px;
283
+ }
284
+
285
+ .session-info {
286
+ background: #e3f2fd;
287
+ padding: 15px;
288
+ border-radius: 10px;
289
+ margin-bottom: 20px;
290
+ }
291
+
292
+ .hidden {
293
+ display: none;
294
+ }
295
+
296
+ .progress-bar {
297
+ width: 100%;
298
+ height: 6px;
299
+ background: #e9ecef;
300
+ border-radius: 3px;
301
+ overflow: hidden;
302
+ margin-top: 15px;
303
+ }
304
+
305
+ .progress-fill {
306
+ height: 100%;
307
+ background: #4285f4;
308
+ width: 0%;
309
+ transition: width 0.3s ease;
310
+ }
311
+
312
+ @media (max-width: 768px) {
313
+ .container {
314
+ padding: 10px;
315
+ }
316
+
317
+ .tabs {
318
+ flex-direction: column;
319
+ }
320
+
321
+ .tab-content {
322
+ padding: 20px;
323
+ }
324
+
325
+ .header h1 {
326
+ font-size: 2rem;
327
+ }
328
+
329
+ .receipt-header {
330
+ flex-direction: column;
331
+ align-items: flex-start;
332
+ }
333
+ }
334
+ </style>
335
+ </head>
336
+ <body>
337
+ <div class="container">
338
+ <div class="header">
339
+ <h1>Qx Intelligent Receipt Processing</h1>
340
+ <p>AI-Powered Receipt Analysis & Categorization</p>
341
+ </div>
342
+
343
+ <div class="main-content">
344
+ <div class="tabs">
345
+ <button class="tab active" onclick="switchTab('single')">Single Receipt</button>
346
+ <button class="tab" onclick="switchTab('multipart')">Multi-Part Receipt</button>
347
+ <button class="tab" onclick="switchTab('bulk')">Bulk Processing</button>
348
+ </div>
349
+
350
+ <!-- Single Receipt Tab -->
351
+ <div id="single-tab" class="tab-content active">
352
+ <div class="upload-area" onclick="document.getElementById('single-file').click()" ondrop="handleDrop(event, 'single')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)">
353
+ <div class="upload-icon">📄</div>
354
+ <h3>Upload Receipt Image</h3>
355
+ <p>Click here or drag and drop your receipt image</p>
356
+ <input type="file" id="single-file" accept="image/*" style="display: none;" onchange="handleSingleFile(this)">
357
+ </div>
358
+ <div class="text-center">
359
+ <button class="btn" onclick="processSingleReceipt()" id="single-process-btn" disabled>Process Receipt</button>
360
+ </div>
361
+ <div id="single-loading" class="loading">
362
+ <div class="spinner"></div>
363
+ <p>Processing receipt...</p>
364
+ </div>
365
+ <div id="single-results" class="results"></div>
366
+ </div>
367
+
368
+ <!-- Multi-Part Receipt Tab -->
369
+ <div id="multipart-tab" class="tab-content">
370
+ <div class="session-info hidden" id="session-info">
371
+ <h4>Session Active</h4>
372
+ <p>Session ID: <span id="session-id"></span></p>
373
+ <p>Parts uploaded: <span id="parts-count">0</span></p>
374
+ </div>
375
+
376
+ <div class="upload-area" onclick="document.getElementById('multipart-file').click()" ondrop="handleDrop(event, 'multipart')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)">
377
+ <div class="upload-icon">📄</div>
378
+ <h3>Upload Receipt Parts</h3>
379
+ <p>Upload multiple images of the same long receipt</p>
380
+ <input type="file" id="multipart-file" accept="image/*" style="display: none;" onchange="handleMultipartFile(this)">
381
+ </div>
382
+
383
+ <div class="text-center">
384
+ <button class="btn btn-secondary" onclick="startSession()" id="start-session-btn">Start New Session</button>
385
+ <button class="btn" onclick="addReceiptPart()" id="add-part-btn" disabled>Add Part</button>
386
+ <button class="btn btn-success" onclick="processMultipartReceipt()" id="multipart-process-btn" disabled>Process Complete Receipt</button>
387
+ </div>
388
+
389
+ <div id="multipart-loading" class="loading">
390
+ <div class="spinner"></div>
391
+ <p>Processing multi-part receipt...</p>
392
+ </div>
393
+ <div id="multipart-results" class="results"></div>
394
+ </div>
395
+
396
+ <!-- Bulk Processing Tab -->
397
+ <div id="bulk-tab" class="tab-content">
398
+ <div class="upload-area" onclick="document.getElementById('bulk-files').click()" ondrop="handleDrop(event, 'bulk')" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)">
399
+ <div class="upload-icon">📁</div>
400
+ <h3>Upload Multiple Receipts</h3>
401
+ <p>Select multiple receipt images for bulk processing</p>
402
+ <input type="file" id="bulk-files" accept="image/*" multiple style="display: none;" onchange="handleBulkFiles(this)">
403
+ </div>
404
+
405
+ <div id="bulk-file-list" class="file-list hidden"></div>
406
+
407
+ <div class="text-center">
408
+ <button class="btn" onclick="processBulkReceipts()" id="bulk-process-btn" disabled>Process All Receipts</button>
409
+ </div>
410
+
411
+ <div id="bulk-loading" class="loading">
412
+ <div class="spinner"></div>
413
+ <p>Processing receipts...</p>
414
+ <div class="progress-bar">
415
+ <div class="progress-fill" id="bulk-progress"></div>
416
+ </div>
417
+ </div>
418
+ <div id="bulk-results" class="results"></div>
419
+ </div>
420
+ </div>
421
+ </div>
422
+
423
+ <script>
424
+ const API_BASE_URL = 'https://rairo-pos-image-api.hf.space';
425
+
426
+ let currentSessionId = null;
427
+ let singleFile = null;
428
+ let multipartFile = null;
429
+ let bulkFiles = [];
430
+
431
+ function switchTab(tabName) {
432
+ // Hide all tabs
433
+ document.querySelectorAll('.tab-content').forEach(tab => {
434
+ tab.classList.remove('active');
435
+ });
436
+ document.querySelectorAll('.tab').forEach(tab => {
437
+ tab.classList.remove('active');
438
+ });
439
+
440
+ // Show selected tab
441
+ document.getElementById(tabName + '-tab').classList.add('active');
442
+ event.target.classList.add('active');
443
+
444
+ // Clear results
445
+ clearResults();
446
+ }
447
+
448
+ function clearResults() {
449
+ document.querySelectorAll('.results').forEach(result => {
450
+ result.innerHTML = '';
451
+ });
452
+ }
453
+
454
+ function handleDragOver(event) {
455
+ event.preventDefault();
456
+ event.currentTarget.classList.add('dragover');
457
+ }
458
+
459
+ function handleDragLeave(event) {
460
+ event.currentTarget.classList.remove('dragover');
461
+ }
462
+
463
+ function handleDrop(event, type) {
464
+ event.preventDefault();
465
+ event.currentTarget.classList.remove('dragover');
466
+
467
+ const files = event.dataTransfer.files;
468
+ if (files.length > 0) {
469
+ if (type === 'bulk') {
470
+ handleBulkFiles({files: files});
471
+ } else {
472
+ const input = document.getElementById(type + '-file');
473
+ input.files = files;
474
+ if (type === 'single') {
475
+ handleSingleFile(input);
476
+ } else {
477
+ handleMultipartFile(input);
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ function handleSingleFile(input) {
484
+ if (input.files && input.files[0]) {
485
+ singleFile = input.files[0];
486
+ document.getElementById('single-process-btn').disabled = false;
487
+ showMessage('single-results', `Selected: ${singleFile.name}`, 'success');
488
+ }
489
+ }
490
+
491
+ function handleMultipartFile(input) {
492
+ if (input.files && input.files[0]) {
493
+ multipartFile = input.files[0];
494
+ document.getElementById('add-part-btn').disabled = currentSessionId === null;
495
+ }
496
+ }
497
+
498
+ function handleBulkFiles(input) {
499
+ if (input.files && input.files.length > 0) {
500
+ bulkFiles = Array.from(input.files);
501
+ document.getElementById('bulk-process-btn').disabled = false;
502
+ displayBulkFileList();
503
+ document.getElementById('bulk-file-list').classList.remove('hidden');
504
+ }
505
+ }
506
+
507
+ function displayBulkFileList() {
508
+ const listContainer = document.getElementById('bulk-file-list');
509
+ listContainer.innerHTML = '<h4>Selected Files:</h4>';
510
+
511
+ bulkFiles.forEach((file, index) => {
512
+ const fileItem = document.createElement('div');
513
+ fileItem.className = 'file-item';
514
+ fileItem.innerHTML = `
515
+ <span>${file.name}</span>
516
+ <button class="btn btn-secondary" onclick="removeBulkFile(${index})" style="padding: 5px 10px; font-size: 0.8rem;">Remove</button>
517
+ `;
518
+ listContainer.appendChild(fileItem);
519
+ });
520
+ }
521
+
522
+ function removeBulkFile(index) {
523
+ bulkFiles.splice(index, 1);
524
+ if (bulkFiles.length === 0) {
525
+ document.getElementById('bulk-file-list').classList.add('hidden');
526
+ document.getElementById('bulk-process-btn').disabled = true;
527
+ } else {
528
+ displayBulkFileList();
529
+ }
530
+ }
531
+
532
+ async function processSingleReceipt() {
533
+ if (!singleFile) return;
534
+
535
+ showLoading('single-loading');
536
+ clearResults();
537
+
538
+ const formData = new FormData();
539
+ formData.append('image', singleFile);
540
+
541
+ try {
542
+ const response = await fetch(`${API_BASE_URL}/process-receipt`, {
543
+ method: 'POST',
544
+ body: formData
545
+ });
546
+
547
+ const result = await response.json();
548
+ hideLoading('single-loading');
549
+
550
+ if (result.success) {
551
+ displayReceiptResult('single-results', result.data);
552
+ } else {
553
+ showMessage('single-results', `Error: ${result.error}`, 'error');
554
+ }
555
+ } catch (error) {
556
+ hideLoading('single-loading');
557
+ showMessage('single-results', `Network error: ${error.message}`, 'error');
558
+ }
559
+ }
560
+
561
+ async function startSession() {
562
+ try {
563
+ const response = await fetch(`${API_BASE_URL}/start-receipt-session`, {
564
+ method: 'POST'
565
+ });
566
+
567
+ const result = await response.json();
568
+
569
+ if (result.success) {
570
+ currentSessionId = result.session_id;
571
+ document.getElementById('session-id').textContent = currentSessionId;
572
+ document.getElementById('session-info').classList.remove('hidden');
573
+ document.getElementById('start-session-btn').disabled = true;
574
+ document.getElementById('add-part-btn').disabled = multipartFile === null;
575
+ document.getElementById('multipart-process-btn').disabled = false;
576
+ updatePartsCount(0);
577
+ } else {
578
+ showMessage('multipart-results', `Error: ${result.error}`, 'error');
579
+ }
580
+ } catch (error) {
581
+ showMessage('multipart-results', `Network error: ${error.message}`, 'error');
582
+ }
583
+ }
584
+
585
+ async function addReceiptPart() {
586
+ if (!currentSessionId || !multipartFile) return;
587
+
588
+ const formData = new FormData();
589
+ formData.append('image', multipartFile);
590
+
591
+ try {
592
+ const response = await fetch(`${API_BASE_URL}/add-receipt-part/${currentSessionId}`, {
593
+ method: 'POST',
594
+ body: formData
595
+ });
596
+
597
+ const result = await response.json();
598
+
599
+ if (result.success) {
600
+ updatePartsCount(result.parts_count);
601
+ showMessage('multipart-results', `Part added successfully. Total parts: ${result.parts_count}`, 'success');
602
+ multipartFile = null;
603
+ document.getElementById('multipart-file').value = '';
604
+ document.getElementById('add-part-btn').disabled = true;
605
+ } else {
606
+ showMessage('multipart-results', `Error: ${result.error}`, 'error');
607
+ }
608
+ } catch (error) {
609
+ showMessage('multipart-results', `Network error: ${error.message}`, 'error');
610
+ }
611
+ }
612
+
613
+ async function processMultipartReceipt() {
614
+ if (!currentSessionId) return;
615
+
616
+ showLoading('multipart-loading');
617
+ clearResults();
618
+
619
+ try {
620
+ const response = await fetch(`${API_BASE_URL}/process-receipt-session/${currentSessionId}`, {
621
+ method: 'POST'
622
+ });
623
+
624
+ const result = await response.json();
625
+ hideLoading('multipart-loading');
626
+
627
+ if (result.success) {
628
+ displayReceiptResult('multipart-results', result.data);
629
+ resetMultipartSession();
630
+ } else {
631
+ showMessage('multipart-results', `Error: ${result.error}`, 'error');
632
+ }
633
+ } catch (error) {
634
+ hideLoading('multipart-loading');
635
+ showMessage('multipart-results', `Network error: ${error.message}`, 'error');
636
+ }
637
+ }
638
+
639
+ async function processBulkReceipts() {
640
+ if (bulkFiles.length === 0) return;
641
+
642
+ showLoading('bulk-loading');
643
+ clearResults();
644
+
645
+ const formData = new FormData();
646
+ bulkFiles.forEach(file => {
647
+ formData.append('images', file);
648
+ });
649
+
650
+ try {
651
+ const response = await fetch(`${API_BASE_URL}/bulk-process-receipts`, {
652
+ method: 'POST',
653
+ body: formData
654
+ });
655
+
656
+ const result = await response.json();
657
+ hideLoading('bulk-loading');
658
+
659
+ if (result.success) {
660
+ displayBulkResults('bulk-results', result);
661
+ } else {
662
+ showMessage('bulk-results', `Error: ${result.error}`, 'error');
663
+ }
664
+ } catch (error) {
665
+ hideLoading('bulk-loading');
666
+ showMessage('bulk-results', `Network error: ${error.message}`, 'error');
667
+ }
668
+ }
669
+
670
+ function displayReceiptResult(containerId, receiptData) {
671
+ const container = document.getElementById(containerId);
672
+
673
+ const receiptCard = document.createElement('div');
674
+ receiptCard.className = 'receipt-card';
675
+
676
+ receiptCard.innerHTML = `
677
+ <div class="receipt-header">
678
+ <div>
679
+ <div class="store-name">${receiptData.store_name || 'Unknown Store'}</div>
680
+ <div class="receipt-date">${receiptData.receipt_date || 'No date'}</div>
681
+ </div>
682
+ <div class="total-amount">Total: $${receiptData.total_amount || '0.00'}</div>
683
+ </div>
684
+ <table class="items-table">
685
+ <thead>
686
+ <tr>
687
+ <th>Item</th>
688
+ <th>Qty</th>
689
+ <th>Unit Price</th>
690
+ <th>Total</th>
691
+ <th>Category</th>
692
+ </tr>
693
+ </thead>
694
+ <tbody>
695
+ ${receiptData.items ? receiptData.items.map(item => `
696
+ <tr>
697
+ <td>${item.name}</td>
698
+ <td>${item.quantity}</td>
699
+ <td>$${item.unit_price}</td>
700
+ <td>$${item.total_price}</td>
701
+ <td><span class="category-${item.category}">${item.category.toUpperCase()}</span></td>
702
+ </tr>
703
+ `).join('') : '<tr><td colspan="5">No items found</td></tr>'}
704
+ </tbody>
705
+ </table>
706
+ `;
707
+
708
+ container.appendChild(receiptCard);
709
+ }
710
+
711
+ function displayBulkResults(containerId, bulkResult) {
712
+ const container = document.getElementById(containerId);
713
+
714
+ const summaryDiv = document.createElement('div');
715
+ summaryDiv.className = 'success';
716
+ summaryDiv.innerHTML = `
717
+ <h4>Bulk Processing Complete</h4>
718
+ <p>Successfully processed: ${bulkResult.processed_count} receipts</p>
719
+ <p>Errors: ${bulkResult.error_count}</p>
720
+ `;
721
+ container.appendChild(summaryDiv);
722
+
723
+ if (bulkResult.errors && bulkResult.errors.length > 0) {
724
+ const errorsDiv = document.createElement('div');
725
+ errorsDiv.className = 'error';
726
+ errorsDiv.innerHTML = `
727
+ <h5>Errors:</h5>
728
+ <ul>
729
+ ${bulkResult.errors.map(error => `<li>${error}</li>`).join('')}
730
+ </ul>
731
+ `;
732
+ container.appendChild(errorsDiv);
733
+ }
734
+
735
+ if (bulkResult.results && bulkResult.results.length > 0) {
736
+ bulkResult.results.forEach((result, index) => {
737
+ const resultDiv = document.createElement('div');
738
+ resultDiv.innerHTML = `<h4>Receipt ${result.file_index}: ${result.filename}</h4>`;
739
+ container.appendChild(resultDiv);
740
+ displayReceiptResult(containerId, result.data);
741
+ });
742
+ }
743
+ }
744
+
745
+ function updatePartsCount(count) {
746
+ document.getElementById('parts-count').textContent = count;
747
+ }
748
+
749
+ function resetMultipartSession() {
750
+ currentSessionId = null;
751
+ document.getElementById('session-info').classList.add('hidden');
752
+ document.getElementById('start-session-btn').disabled = false;
753
+ document.getElementById('add-part-btn').disabled = true;
754
+ document.getElementById('multipart-process-btn').disabled = true;
755
+ document.getElementById('multipart-file').value = '';
756
+ multipartFile = null;
757
+ }
758
+
759
+ function showLoading(loadingId) {
760
+ document.getElementById(loadingId).style.display = 'block';
761
+ }
762
+
763
+ function hideLoading(loadingId) {
764
+ document.getElementById(loadingId).style.display = 'none';
765
+ }
766
+
767
+ function showMessage(containerId, message, type) {
768
+ const container = document.getElementById(containerId);
769
+ const messageDiv = document.createElement('div');
770
+ messageDiv.className = type;
771
+ messageDiv.textContent = message;
772
+ container.appendChild(messageDiv);
773
+ }
774
+
775
+ // Initialize
776
+ document.addEventListener('DOMContentLoaded', function() {
777
+ // Prevent default drag behaviors
778
+ document.addEventListener('dragenter', e => e.preventDefault());
779
+ document.addEventListener('dragover', e => e.preventDefault());
780
+ document.addEventListener('dragleave', e => e.preventDefault());
781
+ document.addEventListener('drop', e => e.preventDefault());
782
+ });
783
+ </script>
784
+ </body>
785
+ </html>