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.