tien_nemo

resize box

...@@ -7,7 +7,7 @@ body { ...@@ -7,7 +7,7 @@ body {
7 display: flex; gap: 20px; padding: 20px; 7 display: flex; gap: 20px; padding: 20px;
8 } 8 }
9 9
10 -.left-panel { 10 +.right-panel {
11 width: 500px; background: #fff; padding: 15px; 11 width: 500px; background: #fff; padding: 15px;
12 border-radius: 8px; box-shadow: 0 0 5px rgba(0,0,0,0.1); 12 border-radius: 8px; box-shadow: 0 0 5px rgba(0,0,0,0.1);
13 } 13 }
...@@ -27,7 +27,7 @@ body { ...@@ -27,7 +27,7 @@ body {
27 border-radius: 4px; 27 border-radius: 4px;
28 } 28 }
29 29
30 -.right-panel { 30 +.left-panel {
31 flex: 1; 31 flex: 1;
32 position: relative; 32 position: relative;
33 background: #eee; 33 background: #eee;
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
9 <body> 9 <body>
10 <meta name="csrf-token" content="{{ csrf_token() }}"> 10 <meta name="csrf-token" content="{{ csrf_token() }}">
11 <div id="app"> 11 <div id="app">
12 - <div class="right-panel" > 12 + <div class="left-panel" >
13 <div class="pdf-container" ref="pdfContainer" 13 <div class="pdf-container" ref="pdfContainer"
14 @mousedown="startSelect" 14 @mousedown="startSelect"
15 @mousemove="onSelect" 15 @mousemove="onSelect"
...@@ -33,6 +33,7 @@ ...@@ -33,6 +33,7 @@
33 v-if="!item.isDeleted" 33 v-if="!item.isDeleted"
34 class="bbox" 34 class="bbox"
35 :class="{ active: index === activeIndex }" 35 :class="{ active: index === activeIndex }"
36 + :data-index="index"
36 :data-field="item.field" 37 :data-field="item.field"
37 :style="getBoxStyle(item, index)" 38 :style="getBoxStyle(item, index)"
38 @click="onBoxClick(index)"> 39 @click="onBoxClick(index)">
...@@ -94,13 +95,12 @@ ...@@ -94,13 +95,12 @@
94 </div> 95 </div>
95 </div> 96 </div>
96 97
97 - <div class="left-panel"> 98 + <div class="right-panel">
98 <div v-for="field in fieldOptions" :key="field.value" class="form-group"> 99 <div v-for="field in fieldOptions" :key="field.value" class="form-group">
99 <label>@{{ field.label }}</label> 100 <label>@{{ field.label }}</label>
100 <input v-model="formData[field.value]" 101 <input v-model="formData[field.value]"
101 @click="onInputClick(field.value)" 102 @click="onInputClick(field.value)"
102 @blur="removeAllFocus()" 103 @blur="removeAllFocus()"
103 -
104 > 104 >
105 </div> 105 </div>
106 <button @click="saveTemplate">💾Save</button> 106 <button @click="saveTemplate">💾Save</button>
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
141 //Thêm event listener để xóa focus khi click ra ngoài 141 //Thêm event listener để xóa focus khi click ra ngoài
142 document.addEventListener('click', (e) => { 142 document.addEventListener('click', (e) => {
143 if ( 143 if (
144 - !e.target.closest('.left-panel') && 144 + !e.target.closest('.right-panel') &&
145 !e.target.closest('.bbox') && 145 !e.target.closest('.bbox') &&
146 !e.target.closest('select') 146 !e.target.closest('select')
147 ) { 147 ) {
...@@ -169,7 +169,6 @@ ...@@ -169,7 +169,6 @@
169 document.addEventListener("mousemove", this.onResizing); 169 document.addEventListener("mousemove", this.onResizing);
170 document.addEventListener("mouseup", this.stopResize); 170 document.addEventListener("mouseup", this.stopResize);
171 }, 171 },
172 -
173 onResizing(e) { 172 onResizing(e) {
174 if (!this.resizing) return; 173 if (!this.resizing) return;
175 174
...@@ -197,129 +196,70 @@ ...@@ -197,129 +196,70 @@
197 x2 += dx; 196 x2 += dx;
198 } 197 }
199 198
200 -
201 // giữ không cho x1 > x2, y1 > y2 199 // giữ không cho x1 > x2, y1 > y2
202 if (x1 < x2 && y1 < y2) { 200 if (x1 < x2 && y1 < y2) {
203 - this.$set(this.ocrData[index], "bbox", [x1, y1, x2, y2]); 201 + const scaleX = this.$refs.pdfImage.clientWidth / this.imageWidth;
202 + const scaleY = this.$refs.pdfImage.clientHeight / this.imageHeight;
203 + const newBbox = [
204 + Math.round(x1),
205 + Math.round(y1),
206 + Math.round(x2),
207 + Math.round(y2)
208 + ];
209 + if (this.ocrData[index].isManual) {
210 + this.$set(this.ocrData[index], "bbox", newBbox);
211 + } else {
212 + this.selectBox = {
213 + ...this.selectBox,
214 + x: Math.round(x1 * scaleX),
215 + y: Math.round(y1 * scaleY),
216 + width: Math.round((x2 - x1) * scaleX),
217 + height: Math.round((y2 - y1) * scaleY),
218 + show: true,
219 + };
220 + this.resizing.newBbox = newBbox;
221 + }
204 } 222 }
205 - },
206 223
224 + },
207 stopResize() { 225 stopResize() {
208 document.removeEventListener("mousemove", this.onResizing); 226 document.removeEventListener("mousemove", this.onResizing);
209 document.removeEventListener("mouseup", this.stopResize); 227 document.removeEventListener("mouseup", this.stopResize);
210 - this.resizing = null;
211 - },
212 -
213 - // Map field cho box (không set active, chỉ dùng để load data từ DB)
214 - mapFieldToBox(index, fieldName, text = null) {
215 - if (index == null) return;
216 - // Xóa tất cả box có fieldName trùng lặp, chỉ giữ lại box hiện tại
217 - this.ocrData = this.ocrData.filter((box, i) => {
218 - if (i === index) return true;
219 - if (box.field === fieldName) {
220 - return false;
221 - }
222 - return true;
223 - });
224 228
225 - // Cập nhật lại index sau khi filter 229 + if (!this.resizing) return;
226 - const newIndex = this.ocrData.findIndex(box => box.bbox === this.ocrData[index]?.bbox); 230 + const { index, newBbox } = this.resizing;
227 - if (newIndex === -1) return; 231 + const targetBox = this.ocrData[index];
228 - 232 +
229 - // Nếu box này từng gán field khác thì bỏ reset flag và tọa độ liên quan. 233 + if (targetBox) {
230 - const prev = this.ocrData[newIndex].field; 234 + if (!targetBox.isManual && newBbox) {
231 - if (prev && prev !== fieldName) { 235 + const newBox = {
232 - if (prev === 'customer_name') { 236 + ...targetBox,
233 - this.hasCustomerNameXY = false; 237 + bbox: newBbox,
234 - this.customer_name_xy = ''; 238 + isManual: true,
239 + isDeleted: false,
240 + showDelete: true,
241 + text: "",
242 + };
243 + this.$set(this.ocrData[index], "isDeleted", true);
244 + this.ocrData.push(newBox);
245 + this.selectingIndex = this.ocrData.length - 1;
246 + this.updateHiddenBorders(newBox.bbox);
247 +
248 + } else if (targetBox.isManual) {
249 + this.updateHiddenBorders(targetBox.bbox);
235 } 250 }
236 - this.ocrData[newIndex].field = null;
237 - this.ocrData[newIndex].field_xy = null;
238 } 251 }
239 252
240 - const bbox = this.ocrData[newIndex].bbox; 253 + this.resizing = null;
241 - 254 + this.selectBox.show = false;
242 - const x1 = bbox[0];
243 - const y1 = bbox[1];
244 - const w = bbox[2];
245 - const h = bbox[3];
246 -
247 - const xyStr = `${x1},${y1},${w},${h}`;
248 - this.ocrData[newIndex].field = fieldName;
249 - this.ocrData[newIndex].field_xy = xyStr;
250 -
251 - this.formData[fieldName] = (text !== null ? text : (this.ocrData[newIndex].text || '')).trim();
252 -
253 - if (fieldName === 'customer_name') {
254 - this.hasCustomerNameXY = true;
255 - this.customer_name_xy = xyStr;
256 - }
257 }, 255 },
258 - 256 + updateHiddenBorders(manualBox) {
259 - async saveTemplate() { 257 + this.ocrData.forEach(item => {
260 - 258 + if (!item.isManual) {
261 - let customer_name = null; 259 + item.hideBorder = this.isBoxInside(item.bbox, manualBox);
262 - let customer_coords = null;
263 - let fields = [];
264 - if (this.manualBoxData.customer_name) {
265 - // Lấy từ manualBoxData nếu có
266 - customer_name = this.manualBoxData.customer_name.text;
267 - customer_coords = this.manualBoxData.customer_name.coords.join(',');
268 - fields = this.manualBoxData;
269 - console.log('Using manualBoxData for customer_name:', customer_name, customer_coords);
270 - } else {
271 - const found = this.ocrData.find(item => item.field === 'customer_name');
272 - if (found) {
273 - customer_name = found.text;
274 - customer_coords = found.field_xy;
275 - }
276 -
277 - const fieldsByName = {};
278 - this.ocrData.forEach(box => {
279 - if (box.field && !box.isDeleted) {
280 - fieldsByName[box.field] = {
281 - text: box.field,
282 - coords: box.field_xy || ''
283 - };
284 - }
285 - });
286 - fields = (fieldsByName);
287 - console.log('Using ocrData for customer_name:', customer_name, customer_coords);
288 - }
289 -
290 - if (!customer_coords || !customer_name) {
291 - alert("Bạn phải map customer_name (quét/select) trước khi lưu.");
292 - return;
293 - }
294 -
295 - const payload = {
296 - customer_name_text: customer_name || '',
297 - template_name: this.formData.template_name || this.formData.customer_name,
298 - customer_name_xy: customer_coords || [],
299 - fields: fields
300 - };
301 -
302 - try {
303 - const res = await fetch('/ocr/save-template', {
304 - method: 'POST',
305 - headers: {
306 - 'Content-Type': 'application/json',
307 - 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
308 - },
309 - body: JSON.stringify(payload)
310 - });
311 - const json = await res.json();
312 - if (json.success) {
313 - alert(json.message);
314 - } else {
315 - alert('Save failed');
316 } 260 }
317 - } catch (err) { 261 + });
318 - console.error(err);
319 - alert('Save error');
320 - }
321 }, 262 },
322 -
323 deleteBox(index) { 263 deleteBox(index) {
324 const item = this.ocrData[index]; 264 const item = this.ocrData[index];
325 if (item.isManual) { 265 if (item.isManual) {
...@@ -345,30 +285,6 @@ ...@@ -345,30 +285,6 @@
345 this.selectingIndex = null; 285 this.selectingIndex = null;
346 } 286 }
347 }, 287 },
348 -
349 -
350 - async loadOCRData() {
351 -
352 - try {
353 - const res = await fetch(`/ocr/data-list`);
354 - const data = await res.json();
355 -
356 - if (data.error) {
357 - console.error('Error loading data:', data.error);
358 - return;
359 - }
360 -
361 - this.ocrData = data.ocrData;
362 - this.pdfImageUrl = data.pdfImageUrl;
363 - this.fieldOptions = data.fieldOptions;
364 - this.dataMapping = data.dataMapping;
365 - this.is_template = data.is_template;
366 -
367 - } catch (error) {
368 - console.error('Error in loadOCRData:', error);
369 - }
370 - },
371 -
372 // Xử lý data sau khi image đã load 288 // Xử lý data sau khi image đã load
373 processLoadedData() { 289 processLoadedData() {
374 this.autoMapFieldsFromFormData(); 290 this.autoMapFieldsFromFormData();
...@@ -377,7 +293,6 @@ ...@@ -377,7 +293,6 @@
377 this.$forceUpdate(); 293 this.$forceUpdate();
378 }); 294 });
379 }, 295 },
380 -
381 autoMapFieldsFromFormData() { 296 autoMapFieldsFromFormData() {
382 this.manualBoxData = {}; 297 this.manualBoxData = {};
383 if(this.is_template) { 298 if(this.is_template) {
...@@ -417,7 +332,6 @@ ...@@ -417,7 +332,6 @@
417 }); 332 });
418 333
419 }, 334 },
420 -
421 onImageLoad() { 335 onImageLoad() {
422 const img = this.$refs.pdfImage; 336 const img = this.$refs.pdfImage;
423 this.imageWidth = img.naturalWidth; 337 this.imageWidth = img.naturalWidth;
...@@ -484,13 +398,7 @@ ...@@ -484,13 +398,7 @@
484 } 398 }
485 399
486 if (coords) { 400 if (coords) {
487 - if (isFromDB) { 401 + this.createManualBoxFromDB(field, coords, text);
488 - // Tạo box manual từ DB (không có nút xóa)
489 - this.createManualBoxFromDB(field, coords, text);
490 - } else {
491 - // Hiển thị lại box manual đã quét chọn (có nút xóa)
492 - this.showManualBox(field, coords, text);
493 - }
494 } 402 }
495 403
496 // Tìm lại index của box để set active 404 // Tìm lại index của box để set active
...@@ -512,7 +420,6 @@ ...@@ -512,7 +420,6 @@
512 this.activeIndex = null; 420 this.activeIndex = null;
513 } 421 }
514 }, 422 },
515 -
516 scrollToBox(index) { 423 scrollToBox(index) {
517 if (!this.$refs.pdfContainer || index < 0 || index >= this.ocrData.length) return; 424 if (!this.$refs.pdfContainer || index < 0 || index >= this.ocrData.length) return;
518 425
...@@ -547,7 +454,6 @@ ...@@ -547,7 +454,6 @@
547 behavior: 'smooth' 454 behavior: 'smooth'
548 }); 455 });
549 }, 456 },
550 -
551 // Xử lý khi click vào input 457 // Xử lý khi click vào input
552 onInputClick(fieldName) { 458 onInputClick(fieldName) {
553 // Kiểm tra xem field này có data không 459 // Kiểm tra xem field này có data không
...@@ -557,30 +463,26 @@ ...@@ -557,30 +463,26 @@
557 this.highlightField(fieldName); 463 this.highlightField(fieldName);
558 } 464 }
559 }, 465 },
560 -
561 // Xóa tất cả focus 466 // Xóa tất cả focus
562 removeAllFocus() { 467 removeAllFocus() {
563 // Reset active index (chỉ mất màu xanh, không xóa box) 468 // Reset active index (chỉ mất màu xanh, không xóa box)
564 this.activeIndex = null; 469 this.activeIndex = null;
565 this.selectingIndex = null; 470 this.selectingIndex = null;
566 -
567 - // Ẩn hoàn toàn các box manual được tạo từ DB (không phải quét chọn thủ công)
568 - this.ocrData.forEach(item => {
569 - if (item.isManual && !item.showDelete) {
570 - // Box manual từ DB (không có nút xóa) - ẩn hoàn toàn
571 - item.isDeleted = true;
572 - }
573 - });
574 -
575 // Đảm bảo tất cả box OCR đều hiển thị (chỉ ẩn border khi cần thiết) 471 // Đảm bảo tất cả box OCR đều hiển thị (chỉ ẩn border khi cần thiết)
576 this.ocrData.forEach(item => { 472 this.ocrData.forEach(item => {
577 - if (!item.isManual && item.hideBorder) { 473 + // if (!item.isManual && item.hideBorder) {
578 - // Chỉ ẩn border cho box OCR nằm trong vùng manual 474 + // item.hideBorder = true;
579 - item.hideBorder = true; 475 + // }
476 + if (item.isManual) {
477 + if (!item.showDelete) {
478 + item.isDeleted = true;
479 + } else {
480 + item.hideBorder = false;
481 + }
482 + // Hiển thị lại border cho manual box
580 } 483 }
581 }); 484 });
582 }, 485 },
583 -
584 // Xử lý khi click vào box 486 // Xử lý khi click vào box
585 onBoxClick(index) { 487 onBoxClick(index) {
586 const item = this.ocrData[index]; 488 const item = this.ocrData[index];
...@@ -631,7 +533,6 @@ ...@@ -631,7 +533,6 @@
631 this.selectBox.showDropdown = false; 533 this.selectBox.showDropdown = false;
632 this.manualField = ""; 534 this.manualField = "";
633 }, 535 },
634 -
635 onSelect(e) { 536 onSelect(e) {
636 if (!this.isSelecting) return; 537 if (!this.isSelecting) return;
637 const rect = this.$refs.pdfContainer.getBoundingClientRect(); 538 const rect = this.$refs.pdfContainer.getBoundingClientRect();
...@@ -642,7 +543,6 @@ ...@@ -642,7 +543,6 @@
642 this.selectBox.width = Math.abs(currentX - this.selectBox.startX); 543 this.selectBox.width = Math.abs(currentX - this.selectBox.startX);
643 this.selectBox.height = Math.abs(currentY - this.selectBox.startY); 544 this.selectBox.height = Math.abs(currentY - this.selectBox.startY);
644 }, 545 },
645 -
646 endSelect(e) { 546 endSelect(e) {
647 if (!this.isSelecting) return; 547 if (!this.isSelecting) return;
648 this.isSelecting = false; 548 this.isSelecting = false;
...@@ -702,12 +602,14 @@ ...@@ -702,12 +602,14 @@
702 const item = this.ocrData[this.selectingIndex]; 602 const item = this.ocrData[this.selectingIndex];
703 if (!item) return; 603 if (!item) return;
704 604
605 + console.log(`Applying mapping for index:`, item);
705 this.ocrData.forEach((box, i) => { 606 this.ocrData.forEach((box, i) => {
706 if (i !== this.selectingIndex && box.field === item.field) { 607 if (i !== this.selectingIndex && box.field === item.field) {
707 box.field = ''; 608 box.field = '';
708 } 609 }
709 }); 610 });
710 if (item.isManual) { 611 if (item.isManual) {
612 + console.log('aaaaaaaaaa')
711 // Nếu là manual box, chuyển sang chế độ manual mapping 613 // Nếu là manual box, chuyển sang chế độ manual mapping
712 this.manualIndex = this.selectingIndex; 614 this.manualIndex = this.selectingIndex;
713 this.manualField = item.field || ""; 615 this.manualField = item.field || "";
...@@ -742,7 +644,7 @@ ...@@ -742,7 +644,7 @@
742 644
743 const newBbox = this.ocrData[manualIndex].bbox; 645 const newBbox = this.ocrData[manualIndex].bbox;
744 646
745 - console.log(`manual for field "${this.manualField}" at index ${manualIndex} with bbox:`, newBbox); 647 + //console.log(`manual for field "${this.manualField}" at index ${manualIndex} with bbox:`, newBbox);
746 this.ocrData.forEach((box, i) => { 648 this.ocrData.forEach((box, i) => {
747 if (i !== manualIndex && box.field === this.ocrData[manualIndex].field) { 649 if (i !== manualIndex && box.field === this.ocrData[manualIndex].field) {
748 box.field = ''; 650 box.field = '';
...@@ -782,6 +684,8 @@ ...@@ -782,6 +684,8 @@
782 const finalText = combinedText.join(" "); 684 const finalText = combinedText.join(" ");
783 685
784 this.ocrData[manualIndex].field = this.manualField; 686 this.ocrData[manualIndex].field = this.manualField;
687 +
688 + console.log('123',this.ocrData[manualIndex])
785 this.formData[this.manualField] = finalText.trim(); 689 this.formData[this.manualField] = finalText.trim();
786 this.manualBoxData[this.manualField] = { 690 this.manualBoxData[this.manualField] = {
787 coords: newBbox, 691 coords: newBbox,
...@@ -800,8 +704,8 @@ ...@@ -800,8 +704,8 @@
800 this.selectBox.showDropdown = false; 704 this.selectBox.showDropdown = false;
801 this.manualField = ""; 705 this.manualField = "";
802 this.manualIndex = null; 706 this.manualIndex = null;
803 - },
804 707
708 + },
805 isBoxInside(inner, outer) { 709 isBoxInside(inner, outer) {
806 // inner: bbox của OCR item [x1, y1, x2, y2] 710 // inner: bbox của OCR item [x1, y1, x2, y2]
807 // outer: bbox của vùng manual [x1, y1, x2, y2] 711 // outer: bbox của vùng manual [x1, y1, x2, y2]
...@@ -821,13 +725,10 @@ ...@@ -821,13 +725,10 @@
821 inner[3] < outer[1] || // inner.bottom < outer.top → hoàn toàn phía trên 725 inner[3] < outer[1] || // inner.bottom < outer.top → hoàn toàn phía trên
822 inner[1] > outer[3] // inner.top > outer.bottom → hoàn toàn phía dưới 726 inner[1] > outer[3] // inner.top > outer.bottom → hoàn toàn phía dưới
823 ); 727 );
824 - 728 + // console.log(`isBoxInside: inner=${inner}, outer=${outer}, isFullyInside=${isFullyInside}, isOverlapping=${isOverlapping}`);
825 // Trả về true nếu box OCR nằm hoàn toàn trong hoặc giao nhau đáng kể 729 // Trả về true nếu box OCR nằm hoàn toàn trong hoặc giao nhau đáng kể
826 return isFullyInside || isOverlapping; 730 return isFullyInside || isOverlapping;
827 }, 731 },
828 -
829 -
830 -
831 getPartialText(text, bbox, selectBbox) { 732 getPartialText(text, bbox, selectBbox) {
832 const [x1, y1, x2, y2] = bbox; 733 const [x1, y1, x2, y2] = bbox;
833 const [sx1, sy1, sx2, sy2] = selectBbox; 734 const [sx1, sy1, sx2, sy2] = selectBbox;
...@@ -856,8 +757,6 @@ ...@@ -856,8 +757,6 @@
856 zIndex: 9999 757 zIndex: 9999
857 }; 758 };
858 }, 759 },
859 -
860 - // Tạo box manual từ tọa độ trong DB
861 createManualBoxFromDB(fieldName, coordinates, text) { 760 createManualBoxFromDB(fieldName, coordinates, text) {
862 if (!this.imageWidth || !this.imageHeight) { 761 if (!this.imageWidth || !this.imageHeight) {
863 console.log('Cannot create manual box: Image not loaded'); 762 console.log('Cannot create manual box: Image not loaded');
...@@ -888,7 +787,7 @@ ...@@ -888,7 +787,7 @@
888 bbox: coords, 787 bbox: coords,
889 field: fieldName, 788 field: fieldName,
890 isManual: true, 789 isManual: true,
891 - showDelete: false, 790 + showDelete: true,
892 isDeleted: false, 791 isDeleted: false,
893 hideBorder: true 792 hideBorder: true
894 }; 793 };
...@@ -901,55 +800,91 @@ ...@@ -901,55 +800,91 @@
901 console.warn('Invalid coordinates for manual box:', coords); 800 console.warn('Invalid coordinates for manual box:', coords);
902 } 801 }
903 }, 802 },
803 + async loadOCRData() {
904 804
905 - // Hiển thị lại box manual đã quét chọn (có nút xóa) 805 + try {
906 - showManualBox(fieldName, coordinates, text) { 806 + const res = await fetch(`/ocr/data-list`);
907 - if (!this.imageWidth || !this.imageHeight) { 807 + const data = await res.json();
908 - console.log('Cannot show manual box: Image not loaded');
909 - return;
910 - }
911 808
912 - // Parse coordinates 809 + if (data.error) {
913 - let coords; 810 + console.error('Error loading data:', data.error);
914 - if (typeof coordinates === 'string') { 811 + return;
915 - coords = coordinates.split(',').map(Number); 812 + }
916 - } else if (Array.isArray(coordinates)) {
917 - coords = coordinates;
918 - } else {
919 - console.error('Invalid coordinates format:', coordinates);
920 - return;
921 - }
922 813
923 - const [x1, y1, x2, y2] = coords; 814 + this.ocrData = data.ocrData;
815 + this.pdfImageUrl = data.pdfImageUrl;
816 + this.fieldOptions = data.fieldOptions;
817 + this.dataMapping = data.dataMapping;
818 + this.is_template = data.is_template;
819 + console.log('Loaded OCR data:', this.ocrData);
924 820
925 - // Kiểm tra tọa độ có hợp lệ không 821 + } catch (error) {
926 - if (x1 >= 0 && y1 >= 0 && x2 > x1 && y2 > y1 && 822 + console.error('Error in loadOCRData:', error);
927 - x2 <= this.imageWidth && y2 <= this.imageHeight) { 823 + }
824 + },
825 + async saveTemplate() {
928 826
929 - // Xóa box cũ có cùng fieldName trước khi hiển thị lại 827 + let customer_name = null;
930 - this.ocrData = this.ocrData.filter(box => !(box.field === fieldName && box.isManual)); 828 + let customer_coords = null;
829 + let fields = [];
830 + if (this.manualBoxData.customer_name) {
831 + // Lấy từ manualBoxData nếu có
832 + customer_name = this.manualBoxData.customer_name.text;
833 + customer_coords = this.manualBoxData.customer_name.coords.join(',');
834 + fields = this.manualBoxData;
835 + console.log('Using manualBoxData for customer_name:', customer_name, customer_coords);
836 + } else {
837 + const found = this.ocrData.find(item => item.field === 'customer_name');
838 + if (found) {
839 + customer_name = found.text;
840 + customer_coords = found.field_xy;
841 + }
931 842
932 - // Kiểm tra xem đây có phải box quét chọn hay box OCR 843 + const fieldsByName = {};
933 - const isFromOCR = this.manualBoxData[fieldName] && this.manualBoxData[fieldName].isFromOCR; 844 + this.ocrData.forEach(box => {
845 + if (box.field && !box.isDeleted) {
846 + fieldsByName[box.field] = {
847 + text: box.field,
848 + coords: box.field_xy || ''
849 + };
850 + }
851 + });
852 + fields = (fieldsByName);
853 + console.log('Using ocrData for customer_name:', customer_name, customer_coords);
854 + }
934 855
935 - // Hiển thị lại box manual (có nút xóa nếu là quét chọn, không có nút xóa nếu là OCR) 856 + if (!customer_coords || !customer_name) {
936 - const manualBox = { 857 + alert("Bạn phải map customer_name (quét/select) trước khi lưu.");
937 - text: text || '', 858 + return;
938 - bbox: coords, 859 + }
939 - field: fieldName,
940 - isManual: true,
941 - showDelete: !isFromOCR, // Chỉ hiển thị nút xóa nếu KHÔNG phải từ OCR
942 - isDeleted: false,
943 - hideBorder: false
944 - };
945 860
946 - this.ocrData.push(manualBox); 861 + const payload = {
947 - // Force re-render 862 + customer_name_text: customer_name || '',
948 - this.$forceUpdate(); 863 + template_name: this.formData.template_name || this.formData.customer_name,
949 - } else { 864 + customer_name_xy: customer_coords || [],
950 - console.warn('Invalid coordinates for manual box:', coords); 865 + fields: fields
866 + };
867 +
868 + try {
869 + const res = await fetch('/ocr/save-template', {
870 + method: 'POST',
871 + headers: {
872 + 'Content-Type': 'application/json',
873 + 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
874 + },
875 + body: JSON.stringify(payload)
876 + });
877 + const json = await res.json();
878 + if (json.success) {
879 + alert(json.message);
880 + } else {
881 + alert('Save failed');
882 + }
883 + } catch (err) {
884 + console.error(err);
885 + alert('Save error');
951 } 886 }
952 - } 887 + },
953 888
954 } 889 }
955 }); 890 });
......