PageSourceSearch

https://getabf.net/qrtable/js/app.js

js getabf.net collected 2026-10-04 20:21:59 UTC 15,893 bytes, 412 lines download raw bytes

1// ============================================
2// MODULE: Ứng dụng chính - Khởi tạo và kết nối
3// ============================================
4
5function initApp() {
6    const savedData = loadScanData();
7    if (savedData && savedData.length > 0) {
8        currentData = savedData;
9        renderTable();
10    }
11    
12    loadAndDisplayCompanyInfo();
13    loadAndDisplayDeliveryInfo();
14    attachAutoSaveEvents();
15    attachButtonEvents();
16    
17    const manualInput = document.getElementById('manualIndex');
18    if (manualInput) {
19        manualInput.addEventListener('keypress', function(e) {
20            if (e.key === 'Enter') {
21                e.preventDefault();
22                handleManualAdd();
23            }
24        });
25    }
26    
27    initCollapsibleSections();
28    
29    // Khởi tạo dropdown chọn mẫu
30    initCompanyDropdown();
31    initDeliveryDropdown();
32    
33    // Khôi phục Excel từ backup nếu có
34    if (typeof restoreExcelFromBackup === 'function') {
35        const restored = restoreExcelFromBackup();
36        if (restored) {
37            showAppToast('📂 Đã khôi phục phiên làm việc với file Excel', 'success');
38        }
39    }
40    
41    // Gắn sự kiện cho nút Excel
42    attachExcelEvents();
43}
44
45function loadAndDisplayCompanyInfo() {
46    const company = loadCompanyInfo();
47    document.getElementById('companyLine0').value = company.line0 || '';
48    document.getElementById('companyLine1').value = company.line1 || '';
49    document.getElementById('companyLine2').value = company.line2 || '';
50    document.getElementById('companyLine3').value = company.line3 || '';
51}
52
53function loadAndDisplayDeliveryInfo() {
54    const delivery = loadDeliveryInfo();
55    document.getElementById('lotNo').value = delivery.lotNo || '';
56    document.getElementById('customerName').value = delivery.customerName || '';
57    document.getElementById('deliveryAddress').value = delivery.deliveryAddress || '';
58    document.getElementById('receiverPhone').value = delivery.receiverPhone || '';
59    document.getElementById('staffName').value = delivery.staffName || '';
60    document.getElementById('staffPhone').value = delivery.staffPhone || '';
61    document.getElementById('notes').value = delivery.notes || '';
62}
63
64function attachAutoSaveEvents() {
65    const companyFields = ['companyLine0', 'companyLine1', 'companyLine2', 'companyLine3'];
66    companyFields.forEach(id => {
67        const el = document.getElementById(id);
68        if (el) {
69            el.addEventListener('input', function() {
70                const company = loadCompanyInfo();
71                company[id] = this.value;
72                saveCompanyInfo(company);
73            });
74        }
75    });
76    
77    const deliveryFields = ['lotNo', 'customerName', 'deliveryAddress', 'receiverPhone', 'staffName', 'staffPhone', 'notes'];
78    deliveryFields.forEach(id => {
79        const el = document.getElementById(id);
80        if (el) {
81            el.addEventListener('input', function() {
82                const delivery = loadDeliveryInfo();
83                delivery[id] = this.value;
84                saveDeliveryInfo(delivery);
85            });
86        }
87    });
88}
89
90function attachButtonEvents() {
91    document.getElementById('startScanBtn')?.addEventListener('click', startQRScan);
92    document.getElementById('stopScanBtn')?.addEventListener('click', stopQRScan);
93    document.getElementById('addManualBtn')?.addEventListener('click', handleManualAdd);
94    document.getElementById('resetTableBtn')?.addEventListener('click', resetTable);
95    document.getElementById('viewPrintBtn')?.addEventListener('click', generateAndPreviewReport);
96    
97    document.getElementById('resetCompanyBtn')?.addEventListener('click', function() {
98        if (confirm('Đặt lại thông tin công ty về mặc định?')) {
99            const defaultCompany = resetCompanyInfo();
100            document.getElementById('companyLine0').value = defaultCompany.line0;
101            document.getElementById('companyLine1').value = defaultCompany.line1;
102            document.getElementById('companyLine2').value = defaultCompany.line2;
103            document.getElementById('companyLine3').value = defaultCompany.line3;
104            showAppToast('Đã đặt lại thông tin công ty', 'success');
105            
106            // Reset dropdown về "-- Chọn mẫu --"
107            const select = document.getElementById('companyTemplateSelect');
108            if (select) select.value = '';
109        }
110    });
111    
112    document.getElementById('resetDeliveryBtn')?.addEventListener('click', function() {
113        if (confirm('Đặt lại thông tin phiếu về mặc định?')) {
114            const defaultDelivery = resetDeliveryInfo();
115            document.getElementById('lotNo').value = defaultDelivery.lotNo || '';
116            document.getElementById('customerName').value = defaultDelivery.customerName;
117            document.getElementById('deliveryAddress').value = defaultDelivery.deliveryAddress;
118            document.getElementById('receiverPhone').value = defaultDelivery.receiverPhone;
119            document.getElementById('staffName').value = defaultDelivery.staffName;
120            document.getElementById('staffPhone').value = defaultDelivery.staffPhone;
121            document.getElementById('notes').value = defaultDelivery.notes;
122            showAppToast('Đã đặt lại thông tin phiếu', 'success');
123            
124            // Reset dropdown về "-- Chọn mẫu --"
125            const select = document.getElementById('deliveryTemplateSelect');
126            if (select) select.value = '';
127        }
128    });
129}
130
131function handleManualAdd() {
132    const manualInput = document.getElementById('manualIndex');
133    let indexValue = manualInput.value.trim();
134    
135    if (!indexValue) {
136        showAppToast('⚠️ Vui lòng nhập index', 'warning');
137        return;
138    }
139    
140    if (!validateIndex(indexValue)) {
141        showAppToast('❌ Index không hợp lệ', 'error');
142        return;
143    }
144    
145    indexValue = sanitizeInput(indexValue);
146    const currentData = getCurrentData();
147    const isDuplicate = currentData.some(row => row.index === indexValue);
148    
149    if (isDuplicate) {
150        showAppToast(`⚠️ Index ${indexValue} đã tồn tại`, 'warning');
151        manualInput.value = '';
152        manualInput.focus();
153        return;
154    }
155    
156    let newRow;
157    const lotNo = document.getElementById('lotNo')?.value || '';
158    
159    // Ưu tiên lấy từ Excel nếu đang active
160    if (typeof excelState !== 'undefined' && excelState && excelState.isActive) {
161        const excelData = getExcelRowData(indexValue);
162        if (excelData) {
163            newRow = {
164                index: indexValue,
165                nameCode1: excelData.nameCode1,
166                nameCode2: excelData.nameCode2,
167                nameCode3: excelData.nameCode3,
168                label: excelData.label,
169                isModified: false,
170                selected: true
171            };
172            // Cập nhật thời gian và lotNo vào Excel
173            updateExcelRow(indexValue, newRow, lotNo, true);
174        } else {
175            // Index chưa có trong Excel: tạo dòng mới, thêm vào Excel
176            newRow = createManualRow(indexValue);
177            addExcelRow(indexValue, newRow, lotNo);
178        }
179    } else {
180        newRow = createManualRow(indexValue);
181    }
182    
183    addRow(newRow);
184    
185    manualInput.value = '';
186    manualInput.focus();
187    showAppToast(`✅ Đã thêm index: ${indexValue}`, 'success');
188}
189
190function initCollapsibleSections() {
191    const sections = document.querySelectorAll('.collapsible-section');
192    sections.forEach(section => {
193        const header = section.querySelector('.section-header');
194        const content = section.querySelector('.section-content');
195        
196        content.style.display = 'block';
197        
198        header.addEventListener('click', (e) => {
199            // Không toggle nếu click vào nút, select, hoặc bất kỳ phần tá»­ con nào cá»
199§a select
200            if (e.target.tagName === 'BUTTON') return;
201            if (e.target.tagName === 'SELECT') return;
202            if (e.target.closest('select')) return;
203            
204            const isCollapsed = header.classList.contains('collapsed');
205            if (isCollapsed) {
206                header.classList.remove('collapsed');
207                content.style.display = 'block';
208            } else {
209                header.classList.add('collapsed');
210                content.style.display = 'none';
211            }
212        });
213    });
214}
215
216function showAppToast(message, type = 'success') {
217    let toast = document.getElementById('app-toast');
218    if (!toast) {
219        toast = document.createElement('div');
220        toast.id = 'app-toast';
221        toast.style.cssText = `
222            position: fixed;
223            bottom: 20px;
224            left: 50%;
225            transform: translateX(-50%);
226            background: rgba(0,0,0,0.85);
227            color: white;
228            padding: 10px 20px;
229            border-radius: 8px;
230            z-index: 10000;
231            font-size: 14px;
232            white-space: nowrap;
233            transition: opacity 0.3s;
234            pointer-events: none;
235            font-family: 'Segoe UI', Arial, sans-serif;
236        `;
237        document.body.appendChild(toast);
238    }
239    
240    if (type === 'success') {
241        toast.style.background = 'rgba(40, 167, 69, 0.95)';
242    } else if (type === 'error') {
243        toast.style.background = 'rgba(220, 53, 69, 0.95)';
244    } else {
245        toast.style.background = 'rgba(255, 152, 0, 0.95)';
246    }
247    
248    toast.textContent = message;
249    toast.style.opacity = '1';
250    toast.style.whiteSpace = 'normal';
251    toast.style.maxWidth = '90%';
252    toast.style.textAlign = 'center';
253    
254    setTimeout(() => {
255        toast.style.opacity = '0';
256    }, 2000);
257}
258
259// ============================================
260// HÀM XỬ LÝ DROPDOWN CHỌN MẪU CÔNG TY & PHIẾU
261// ============================================
262
263function initCompanyDropdown() {
264    const select = document.getElementById('companyTemplateSelect');
265    if (!select) return;
266    
267    // Xóa các option cũ (giữ lại option đầu tiên "-- Chọn mẫu --")
268    while (select.options.length > 1) {
269        select.remove(1);
270    }
271    
272    const keys = getCompanyTemplateKeys();
273    keys.forEach(key => {
274        const option = document.createElement('option');
275        option.value = key;
276        option.textContent = key;
277        select.appendChild(option);
278    });
279    
280    // Gắn sự kiện khi chọn mẫu
281    select.removeEventListener('change', handleCompanyTemplateChange);
282    select.addEventListener('change', handleCompanyTemplateChange);
283}
284
285function handleCompanyTemplateChange(event) {
286    const select = event.target;
287    const templateName = select.value;
288    
289    if (!templateName) return; // "-- Chọn mẫu --" thì không làm gì
290    
291    const confirmMsg = `🔄 Áp dụng mẫu "${templateName}" sẽ thay thế thông tin công ty hiện tại.\n\nBạn có chắc chắn không?`;
292    if (!confirm(confirmMsg)) {
293        select.value = ''; // Reset về "-- Chọn mẫu --"
294        return;
295    }
296    
297    // Áp dụng mẫu
298    const newCompany = applyCompanyTemplate(templateName);
299    if (newCompany) {
300        // Cập nhật giao diện
301        document.getElementById('companyLine0').value = newCompany.line0 || '';
302        document.getElementById('companyLine1').value = newCompany.line1 || '';
303        document.getElementById('companyLine2').value = newCompany.line2 || '';
304        document.getElementById('companyLine3').value = newCompany.line3 || '';
305        showAppToast(`✅ Đã áp dụng mẫu công ty: ${templateName}`, 'success');
306    }
307    
308    // KHÔNG reset select.value - giữ lại tên mẫu đã chọn để hiển thị
309}
310
311function initDeliveryDropdown() {
312    const select = document.getElementById('deliveryTemplateSelect');
313    if (!select) return;
314    
315    // Xóa các option cũ
316    while (select.options.length > 1) {
317        select.remove(1);
318    }
319    
320    const keys = getDeliveryTemplateKeys();
321    keys.forEach(key => {
322        const option = document.createElement('option');
323        option.value = key;
324        option.textContent = key;
325        select.appendChild(option);
326    });
327    
328    // Gắn sự kiện khi chọn mẫu
329    select.removeEventListener('change', handleDeliveryTemplateChange);
330    select.addEventListener('change', handleDeliveryTemplateChange);
331}
332
333function handleDeliveryTemplateChange(event) {
334    const select = event.target;
335    const templateName = select.value;
336    
337    if (!templateName) return;
338    
339    const confirmMsg = `🔄 Áp dụng mẫu "${templateName}" sẽ thay thế thông tin phiếu hiện tại.\n\nBạn có chắc chắn không?`;
340    if (!confirm(confirmMsg)) {
341        select.value = '';
342        return;
343    }
344    
345    // Áp dụng mẫu
346    const newDelivery = applyDeliveryTemplate(templateName);
347    if (newDelivery) {
348        // Cập nhật giao diện
349        document.getElementById('lotNo').value = newDelivery.lotNo || '';
350        document.getElementById('customerName').value = newDelivery.customerName || '';
351        document.getElementById('deliveryAddress').value = newDelivery.deliveryAddress || '';
352        document.getElementById('receiverPhone').value = newDelivery.receiverPhone || '';
353        document.getElementById('staffName').value = newDelivery.staffName || '';
354        document.getElementById('staffPhone').value = newDelivery.staffPhone || '';
355        document.getElementById('notes').value = newDelivery.notes || '';
356        showAppToast(`✅ Đã áp dụng mẫu phiếu: ${templateName}`, 'success');
357    }
358    
359    // KHÔNG reset select.value - giữ lại tên mẫu đã chọn để hiển thị
360}
361
362// ============================================
363// SỰ KIỆN CHO NÚT EXCEL
364// ============================================
365function attachExcelEvents() {
366    const uploadBtn = document.getElementById('uploadExcelBtn');
367    const exportBtn = document.getElementById('exportExcelBtn');
368    
369    if (uploadBtn) {
370        uploadBtn.addEventListener('click', () => {
371            const input = document.createElement('input');
372            input.type = 'file';
373            input.accept = '.xlsx, .xls';
374            input.onchange = async (e) => {
375                const file = e.target.files[0];
376                if (file) {
377                    // Xóa trạng thái cũ trước khi upload mới
378                    if (typeof clearExcelState === 'function') {
379                        clearExcelState();
380                    }
381                    if (typeof uploadExcelFile === 'function') {
382                        await uploadExcelFile(file);
383                        if (typeof excelState !== 'undefined' && excelState && excelState.isActive) {
384                            showAppToast('✅ Đã sẵn sàng đối chiếu với Excel', 'success');
385                        }
386                    } else {
387                        showAppToast('❌ Thư viện Excel chưa sẵn sàng', 'error');
388                    }
389                }
390            };
391            input.click();
392        });
393    }
394    
395    if (exportBtn) {
396        exportBtn.addEventListener('click', () => {
397            const tableData = getCurrentData();
398            if (!tableData || tableData.length === 0) {
399                showAppToast('⚠️ Không có dữ liệu để xuất Excel', 'warning');
400                return;
401            }
402            const lotNo = document.getElementById('lotNo')?.value || '';
403            if (typeof exportExcelFile === 'function') {
404                exportExcelFile(tableData, lotNo);
405            } else {
406                showAppToast('❌ Chức năng Export chưa sẵn sàng', 'error');
407            }
408        });
409    }
410}
411
412document.addEventListener('DOMContentLoaded', initApp);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.