doeqoth commited on
Commit
8bd16de
·
verified ·
1 Parent(s): da6c2d3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +438 -145
index.html CHANGED
@@ -941,18 +941,24 @@
941
  function normalizeText(text) {
942
  if (!text) return '';
943
  return text.toString().toLowerCase().trim()
944
- .replace(/\s+/g, ' ');
 
 
945
  }
946
 
947
- // Search function supporting Thai, English, and numbers
948
  function searchWorkers(query) {
949
  const normalizedQuery = normalizeText(query);
950
- if (!normalizedQuery) {
951
  filteredData = [...allWorkerData];
952
  return filteredData;
953
  }
954
 
 
 
 
955
  filteredData = allWorkerData.filter(worker => {
 
956
  const searchFields = [
957
  worker.requestNumber,
958
  worker.englishName,
@@ -960,26 +966,119 @@
960
  worker.personalID,
961
  worker.workPermitNumber,
962
  worker.alienReferenceNumber,
963
- worker.nationality
 
 
964
  ];
965
 
966
- return searchFields.some(field => {
967
- const normalizedField = normalizeText(field);
968
- return normalizedField.includes(normalizedQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
969
  });
 
 
 
 
 
 
 
 
 
 
970
  });
971
 
972
  return filteredData;
973
  }
974
 
975
- // Highlight matched text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
976
  function highlightText(text, query) {
977
  if (!text || !query) return text || '';
 
 
978
  const normalizedQuery = normalizeText(query);
979
- if (!normalizedQuery) return text;
 
 
 
980
 
981
- const regex = new RegExp(`(${escapeRegex(query)})`, 'gi');
982
- return text.replace(regex, '<span class="highlight">$1</span>');
 
 
 
 
 
 
 
 
 
 
 
983
  }
984
 
985
  function escapeRegex(string) {
@@ -1003,7 +1102,7 @@
1003
  }
1004
  }
1005
 
1006
- // Render dropdown list
1007
  function renderDropdownList(query = '') {
1008
  const workers = searchWorkers(query);
1009
 
@@ -1015,6 +1114,7 @@
1015
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
1016
  </svg>
1017
  <p>ไม่พบผลลัพธ์สำหรับ "${query}"</p>
 
1018
  </div>
1019
  `;
1020
  return;
@@ -1037,10 +1137,16 @@
1037
  <img class="dropdown-item-image"
1038
  src="${worker.profileImage || 'https://via.placeholder.com/40?text=N/A'}"
1039
  alt="${worker.englishName || 'Worker'}"
 
1040
  onerror="this.src='https://via.placeholder.com/40?text=N/A'"/>
1041
  <div class="dropdown-item-content">
1042
  <div class="dropdown-item-title">${displayName}</div>
1043
  <div class="dropdown-item-subtitle">เลขที่: ${displayId}</div>
 
 
 
 
 
1044
  </div>
1045
  <div class="dropdown-item-checkbox" onclick="event.stopPropagation(); toggleWorkerSelection(${originalIndex})">
1046
  <div class="custom-checkbox ${isChecked ? 'checked' : ''}" role="checkbox" aria-checked="${isChecked}">
@@ -1079,27 +1185,33 @@
1079
 
1080
  // Select a worker to view
1081
  function selectWorker(index) {
1082
- currentIndex = index;
1083
- displayWorkerData(index);
1084
- updateDropdownHeader();
1085
- randomizeBothOverlays();
 
 
 
1086
  }
1087
 
1088
  // Toggle worker selection for PDF download
1089
  function toggleWorkerSelection(index) {
1090
- if (selectedWorkers.has(index)) {
1091
- selectedWorkers.delete(index);
1092
- } else {
1093
- selectedWorkers.add(index);
 
 
 
 
1094
  }
1095
- updateSelectionUI();
1096
- renderDropdownList(searchInput.value);
1097
  }
1098
 
1099
  // Update selection UI
1100
  function updateSelectionUI() {
1101
  const count = selectedWorkers.size;
1102
  selectedCountEl.textContent = `เลือกแล้ว ${count} รายการ`;
 
1103
  downloadSelectedBtn.disabled = count === 0;
1104
  downloadSelectedBtn.innerHTML = `
1105
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -1152,15 +1264,20 @@
1152
 
1153
  searchInput.addEventListener('input', (e) => {
1154
  const query = e.target.value;
1155
- renderDropdownList(query);
1156
 
1157
- if (query) {
1158
- searchIcon.style.display = 'none';
1159
- clearSearch.classList.add('visible');
1160
- } else {
1161
- searchIcon.style.display = 'block';
1162
- clearSearch.classList.remove('visible');
1163
- }
 
 
 
 
 
 
1164
  });
1165
 
1166
  clearSearch.addEventListener('click', () => {
@@ -1181,6 +1298,13 @@
1181
  }
1182
  });
1183
 
 
 
 
 
 
 
 
1184
  // Button event listeners
1185
  grayscaleBtn.addEventListener('click', toggleGrayscale);
1186
  downloadCurrentBtn.addEventListener('click', downloadCurrentPDF);
@@ -1194,12 +1318,26 @@
1194
  console.error(`Image element with ID "${imgElementId}" not found.`);
1195
  return;
1196
  }
1197
- imgElement.src = src || defaultSrc;
 
 
 
 
 
 
 
 
1198
  imgElement.alt = workerName || type;
1199
  imgElement.crossOrigin = "anonymous";
 
1200
  imgElement.onerror = function() {
1201
- imgElement.src = defaultSrc;
1202
- imgElement.alt = 'No Image';
 
 
 
 
 
1203
  };
1204
  }
1205
 
@@ -1427,13 +1565,13 @@
1427
  document.getElementById('alienReferenceReceipt').innerHTML = worker.alienReferenceNumber || 'N/A';
1428
  document.getElementById('personalIDReceipt').innerHTML = worker.personalID || 'N/A';
1429
  setImageSrc('profilePic', worker.profileImage, defaultProfileSrc, worker.englishName, 'profile picture');
 
 
 
 
1430
 
1431
- const currentDomain = window.location.origin;
1432
- const workerPageUrl = `${currentDomain}/worker.html?id=${encodeURIComponent(worker.requestNumber || '')}`;
1433
- const receiptUrl = `${currentDomain}/pdf.html?id=${encodeURIComponent(worker.requestNumber || '')}`;
1434
-
1435
- setImageSrc('qrCode', `https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=${encodeURIComponent(workerPageUrl)}`, defaultQrCodeSrc, worker.englishName, 'work permit QR code');
1436
- setImageSrc('receiptQrCode', `https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=${encodeURIComponent(receiptUrl)}`, defaultReceiptQrCodeSrc, worker.englishName, 'receipt QR code');
1437
 
1438
  const timestamp = getCurrentTimestamp();
1439
  const workPermitTimestamp = `เอกสารอิเล็กทรอนิกส์ฉบับนี้ถูกสร้างจากระบบอนุญาตทำงานคนต่างด้าวที่มีสถานะการทำงานไม่ถูกต้องตามกฎหมาย ตามมติคณะรัฐมนตรีเมื่อวันที่ 24 กันยายน 2567<br>โดยกรมการจัดหางาน กระทรวงแรงงาน<br>พิมพ์เอกสาร วันที่ 10/04/2568 ${timestamp} `;
@@ -1468,8 +1606,18 @@ setImageSrc('receiptQrCode', `https://api.qrserver.com/v1/create-qr-code/?size=3
1468
  const percent = total > 0 ? Math.round((current / total) * 100) : 0;
1469
  progressFill.style.width = `${percent}%`;
1470
  progressText.textContent = `กำลังดาวน์โหลด ${current}/${total} (${percent}%)`;
 
 
 
 
 
 
 
 
 
1471
  } else {
1472
  progressContainer.classList.remove('visible');
 
1473
  }
1474
  }
1475
 
@@ -1482,104 +1630,167 @@ setImageSrc('receiptQrCode', `https://api.qrserver.com/v1/create-qr-code/?size=3
1482
  grayscaleBtn.disabled = disabled;
1483
  }
1484
 
1485
- async function downloadPDF(workerItem, filenameSuffix = '') {
1486
- try {
1487
- randomizeBothOverlays();
1488
- const elements = [document.getElementById('page1-div'), document.getElementById('page2-div')];
1489
- const reqNum = workerItem.requestNumber || 'worker';
1490
- const english = (workerItem.englishName || '').replace(/\s+/g, ''); // ตัดช่องว่างออก
1491
- const last4 = reqNum.slice(-4); // เลขท้าย 4 หลัก
1492
-
1493
- // ถ้าอยากใช้เลขท้าย 4:
1494
- const filename = `${last4}_${english || 'noname'}.pdf`;
1495
-
1496
- const controls = document.querySelector('.controls-container');
1497
- if (controls) controls.style.display = 'none';
1498
-
1499
- if (allWorkerData[currentIndex].requestNumber !== workerItem.requestNumber) {
1500
- const tempIndex = allWorkerData.findIndex(w => w.requestNumber === workerItem.requestNumber);
1501
- if (tempIndex !== -1) displayWorkerData(tempIndex);
1502
- }
1503
-
1504
- const images = document.querySelectorAll('img');
1505
- await Promise.all(Array.from(images).map(img => {
1506
- return new Promise(resolve => {
1507
- if (img.complete && img.naturalWidth > 0) resolve();
1508
- else {
1509
- img.onload = resolve;
1510
- img.onerror = () => {
1511
- console.error(`Image failed to load: ${img.src}`);
1512
- resolve();
1513
- };
1514
  }
1515
- });
1516
- }));
1517
-
1518
- const container = document.createElement('div');
1519
- container.style.width = '892px';
1520
- container.style.height = '2522px';
1521
- container.className = 'pdf-export-container';
1522
-
1523
- const page1Clone = elements[0].cloneNode(true);
1524
- const page2Clone = elements[1].cloneNode(true);
1525
- page1Clone.style.marginBottom = '0';
1526
- page2Clone.style.marginTop = '0';
1527
-
1528
- container.appendChild(page1Clone);
1529
- container.appendChild(page2Clone);
1530
- document.body.appendChild(container);
1531
-
1532
- const allImages = container.querySelectorAll('img');
1533
- allImages.forEach(img => {
1534
- img.style.filter = 'grayscale(100%)';
1535
- img.style.webkitFilter = 'grayscale(100%)';
1536
- });
1537
-
1538
-
1539
- await new Promise(resolve => setTimeout(resolve, 100));
1540
-
1541
- const opt = {
1542
- margin: [0, 0, 0, 0],
1543
- filename: filename,
1544
- image: { type: 'jpeg', quality: 0.98 },
1545
- html2canvas: {
1546
- scale: 3,
1547
- useCORS: true,
1548
- width: 892,
1549
- height: 2522,
1550
- logging: false,
1551
- allowTaint: true,
1552
- backgroundColor: '#ffffff'
1553
- },
1554
- jsPDF: {
1555
- unit: 'px',
1556
- format: [892, 1261],
1557
- orientation: 'portrait',
1558
- compress: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1559
  }
1560
- };
1561
-
1562
- await html2pdf().set(opt).from(container).save();
1563
-
1564
- document.body.removeChild(container);
1565
- if (controls) controls.style.display = 'block';
1566
-
1567
- if (allWorkerData[currentIndex].requestNumber !== workerItem.requestNumber) {
1568
- displayWorkerData(currentIndex);
1569
- }
1570
- } catch (error) {
1571
- console.error('Error generating PDF:', error);
1572
- alert('เกิดข้อผิดพลาดในการสร้าง PDF กรุณาลองใหม่อีกครั้ง');
1573
- const controls = document.querySelector('.controls-container');
1574
- if (controls) controls.style.display = 'block';
1575
- }
1576
- }
1577
 
1578
  async function downloadCurrentPDF() {
1579
  if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1580
  setButtonsDisabled(true);
1581
  try {
1582
- await downloadPDF(allWorkerData[currentIndex]);
1583
  } catch (error) {
1584
  alert('เกิดข้อผิดพลาดในการสร้าง PDF กรุณาลองใหม่อีกครั้ง');
1585
  }
@@ -1596,29 +1807,58 @@ const filename = `${last4}_${english || 'noname'}.pdf`;
1596
  }
1597
 
1598
  const selectedArray = Array.from(selectedWorkers).sort((a, b) => a - b);
1599
- const confirmDownload = confirm(`คุณต้องการดาวน์โหลดใบอนุญาตทำงานสำหรับพนักงาน ${selectedArray.length} รายการใช่หรือไม่?`);
1600
  if (!confirmDownload) return;
1601
 
1602
  setButtonsDisabled(true);
1603
  showProgress(true, 0, selectedArray.length);
1604
 
 
 
 
 
1605
  for (let i = 0; i < selectedArray.length; i++) {
1606
  const workerIndex = selectedArray[i];
 
 
 
 
1607
  try {
1608
- showProgress(true, i + 1, selectedArray.length);
1609
- displayWorkerData(workerIndex);
1610
- await downloadPDF(allWorkerData[workerIndex], `work_permit_${i + 1}`);
1611
- await new Promise(resolve => setTimeout(resolve, 800));
 
 
 
 
 
 
 
 
 
1612
  } catch (error) {
1613
  console.error(`Error downloading PDF for worker at index ${workerIndex}:`, error);
1614
- continue;
 
1615
  }
1616
  }
1617
 
1618
  showProgress(false);
1619
  setButtonsDisabled(false);
1620
- alert(`ดาวน์โหลดใบอนุญาตทำงานเรียบร้อยแล้ว ${selectedArray.length} รายการ`);
1621
 
 
 
 
 
 
 
 
 
 
 
 
 
1622
  if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1623
  displayWorkerData(currentIndex);
1624
  }
@@ -1630,28 +1870,55 @@ const filename = `${last4}_${english || 'noname'}.pdf`;
1630
  return;
1631
  }
1632
 
1633
- const confirmDownload = confirm(`คุณต้องการดาวน์โหลดใบอนุญาตทำงานสำหรับพนักงานทั้งหมด ${allWorkerData.length} รายการใช่หรือไม่?`);
1634
  if (!confirmDownload) return;
1635
 
1636
  setButtonsDisabled(true);
1637
  showProgress(true, 0, allWorkerData.length);
1638
 
 
 
 
 
1639
  for (let i = 0; i < allWorkerData.length; i++) {
 
 
1640
  try {
1641
- showProgress(true, i + 1, allWorkerData.length);
1642
- displayWorkerData(i);
1643
- await downloadPDF(allWorkerData[i], `work_permit_${i + 1}`);
1644
- await new Promise(resolve => setTimeout(resolve, 800));
 
 
 
 
 
 
 
 
 
1645
  } catch (error) {
1646
  console.error(`Error downloading PDF for worker at index ${i}:`, error);
1647
- continue;
 
1648
  }
1649
  }
1650
 
1651
  showProgress(false);
1652
  setButtonsDisabled(false);
1653
- alert(`ดาวน์โหลดใบอนุญาตทำงานเรียบร้อยแล้ว ${allWorkerData.length} รายการ`);
1654
 
 
 
 
 
 
 
 
 
 
 
 
 
1655
  if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1656
  displayWorkerData(currentIndex);
1657
  } else if (allWorkerData.length > 0) {
@@ -1663,10 +1930,36 @@ const filename = `${last4}_${english || 'noname'}.pdf`;
1663
  try {
1664
  await loadWorkerData();
1665
  randomizeBothOverlays();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1666
  } catch (e) {
1667
  console.error('Error initializing application:', e);
1668
  }
1669
  };
1670
- </script>
1671
  </body>
1672
  </html>
 
941
  function normalizeText(text) {
942
  if (!text) return '';
943
  return text.toString().toLowerCase().trim()
944
+ .replace(/[่้๊๋์]/g, '') // Remove Thai tonal marks
945
+ .replace(/\s+/g, ' ') // Normalize spaces
946
+ .normalize('NFD').replace(/[\u0300-\u036f]/g, ''); // Remove accents
947
  }
948
 
949
+ // Advanced search function with multiple strategies
950
  function searchWorkers(query) {
951
  const normalizedQuery = normalizeText(query);
952
+ if (!normalizedQuery || normalizedQuery.length < 1) {
953
  filteredData = [...allWorkerData];
954
  return filteredData;
955
  }
956
 
957
+ // Split query into individual terms
958
+ const searchTerms = normalizedQuery.split(/\s+/).filter(term => term.length > 0);
959
+
960
  filteredData = allWorkerData.filter(worker => {
961
+ // Collect all searchable fields
962
  const searchFields = [
963
  worker.requestNumber,
964
  worker.englishName,
 
966
  worker.personalID,
967
  worker.workPermitNumber,
968
  worker.alienReferenceNumber,
969
+ worker.nationality,
970
+ worker.age,
971
+ worker.birthDate
972
  ];
973
 
974
+ // Normalize all fields
975
+ const normalizedFields = searchFields.map(field => normalizeText(field));
976
+
977
+ // Strategy 1: Exact phrase match
978
+ const phraseMatch = normalizedFields.some(field =>
979
+ field.includes(normalizedQuery)
980
+ );
981
+
982
+ // Strategy 2: All terms match (AND logic)
983
+ const allTermsMatch = searchTerms.every(term =>
984
+ normalizedFields.some(field => field.includes(term))
985
+ );
986
+
987
+ // Strategy 3: Any term match (OR logic)
988
+ const anyTermMatch = searchTerms.some(term =>
989
+ normalizedFields.some(field => field.includes(term))
990
+ );
991
+
992
+ // Strategy 4: Partial match with scoring
993
+ let partialMatchScore = 0;
994
+ searchTerms.forEach(term => {
995
+ normalizedFields.forEach(field => {
996
+ if (field.includes(term)) {
997
+ partialMatchScore++;
998
+ }
999
+ });
1000
  });
1001
+
1002
+ // Return true if any strategy matches
1003
+ return phraseMatch || allTermsMatch || anyTermMatch || partialMatchScore > 0;
1004
+ });
1005
+
1006
+ // Sort by relevance
1007
+ filteredData.sort((a, b) => {
1008
+ const aScore = calculateRelevanceScore(a, normalizedQuery, searchTerms);
1009
+ const bScore = calculateRelevanceScore(b, normalizedQuery, searchTerms);
1010
+ return bScore - aScore;
1011
  });
1012
 
1013
  return filteredData;
1014
  }
1015
 
1016
+ // Calculate relevance score for sorting
1017
+ function calculateRelevanceScore(worker, query, terms) {
1018
+ let score = 0;
1019
+ const fields = [
1020
+ { value: worker.requestNumber, weight: 5 },
1021
+ { value: worker.englishName, weight: 4 },
1022
+ { value: worker.thaiName, weight: 4 },
1023
+ { value: worker.personalID, weight: 3 },
1024
+ { value: worker.workPermitNumber, weight: 3 },
1025
+ { value: worker.alienReferenceNumber, weight: 2 },
1026
+ { value: worker.nationality, weight: 1 }
1027
+ ];
1028
+
1029
+ fields.forEach(field => {
1030
+ const normalizedField = normalizeText(field.value);
1031
+
1032
+ // Exact match bonus
1033
+ if (normalizedField === query) {
1034
+ score += 10 * field.weight;
1035
+ }
1036
+
1037
+ // Contains query
1038
+ if (normalizedField.includes(query)) {
1039
+ score += 5 * field.weight;
1040
+ }
1041
+
1042
+ // Contains all terms
1043
+ if (terms.every(term => normalizedField.includes(term))) {
1044
+ score += 3 * field.weight;
1045
+ }
1046
+
1047
+ // Contains any term
1048
+ terms.forEach(term => {
1049
+ if (normalizedField.includes(term)) {
1050
+ score += field.weight;
1051
+ }
1052
+ });
1053
+ });
1054
+
1055
+ return score;
1056
+ }
1057
+
1058
+ // Highlight matched text with improved accuracy
1059
  function highlightText(text, query) {
1060
  if (!text || !query) return text || '';
1061
+
1062
+ const normalizedText = normalizeText(text);
1063
  const normalizedQuery = normalizeText(query);
1064
+
1065
+ if (!normalizedQuery || !normalizedText.includes(normalizedQuery)) {
1066
+ return text;
1067
+ }
1068
 
1069
+ // Find the actual position in the original text
1070
+ const searchTerms = normalizedQuery.split(/\s+/);
1071
+ let highlightedText = text;
1072
+
1073
+ searchTerms.forEach(term => {
1074
+ if (term.length > 0) {
1075
+ // Create regex that's case-insensitive and handles Thai
1076
+ const regex = new RegExp(`(${escapeRegex(term)})`, 'gi');
1077
+ highlightedText = highlightedText.replace(regex, '<span class="highlight">$1</span>');
1078
+ }
1079
+ });
1080
+
1081
+ return highlightedText;
1082
  }
1083
 
1084
  function escapeRegex(string) {
 
1102
  }
1103
  }
1104
 
1105
+ // Render dropdown list with improved performance
1106
  function renderDropdownList(query = '') {
1107
  const workers = searchWorkers(query);
1108
 
 
1114
  <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
1115
  </svg>
1116
  <p>ไม่พบผลลัพธ์สำหรับ "${query}"</p>
1117
+ <small>ลองค้นหาโดยใช้เลขที่ขออนุญาต, ชื่อ, เลขบัตรประชาชน, หรือเลขใบอนุญาตทำงาน</small>
1118
  </div>
1119
  `;
1120
  return;
 
1137
  <img class="dropdown-item-image"
1138
  src="${worker.profileImage || 'https://via.placeholder.com/40?text=N/A'}"
1139
  alt="${worker.englishName || 'Worker'}"
1140
+ loading="lazy"
1141
  onerror="this.src='https://via.placeholder.com/40?text=N/A'"/>
1142
  <div class="dropdown-item-content">
1143
  <div class="dropdown-item-title">${displayName}</div>
1144
  <div class="dropdown-item-subtitle">เลขที่: ${displayId}</div>
1145
+ <div class="dropdown-item-meta">
1146
+ <span>${worker.nationality || 'N/A'}</span>
1147
+ <span>•</span>
1148
+ <span>${worker.personalID ? worker.personalID.slice(-4) : 'N/A'}</span>
1149
+ </div>
1150
  </div>
1151
  <div class="dropdown-item-checkbox" onclick="event.stopPropagation(); toggleWorkerSelection(${originalIndex})">
1152
  <div class="custom-checkbox ${isChecked ? 'checked' : ''}" role="checkbox" aria-checked="${isChecked}">
 
1185
 
1186
  // Select a worker to view
1187
  function selectWorker(index) {
1188
+ if (index >= 0 && index < allWorkerData.length) {
1189
+ currentIndex = index;
1190
+ displayWorkerData(index);
1191
+ updateDropdownHeader();
1192
+ randomizeBothOverlays();
1193
+ toggleDropdown(true); // Close dropdown after selection
1194
+ }
1195
  }
1196
 
1197
  // Toggle worker selection for PDF download
1198
  function toggleWorkerSelection(index) {
1199
+ if (index >= 0 && index < allWorkerData.length) {
1200
+ if (selectedWorkers.has(index)) {
1201
+ selectedWorkers.delete(index);
1202
+ } else {
1203
+ selectedWorkers.add(index);
1204
+ }
1205
+ updateSelectionUI();
1206
+ renderDropdownList(searchInput.value);
1207
  }
 
 
1208
  }
1209
 
1210
  // Update selection UI
1211
  function updateSelectionUI() {
1212
  const count = selectedWorkers.size;
1213
  selectedCountEl.textContent = `เลือกแล้ว ${count} รายการ`;
1214
+ selectedCountEl.className = count > 0 ? 'has-selection' : '';
1215
  downloadSelectedBtn.disabled = count === 0;
1216
  downloadSelectedBtn.innerHTML = `
1217
  <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
 
1264
 
1265
  searchInput.addEventListener('input', (e) => {
1266
  const query = e.target.value;
 
1267
 
1268
+ // Debounce search for better performance
1269
+ clearTimeout(searchInput.debounceTimer);
1270
+ searchInput.debounceTimer = setTimeout(() => {
1271
+ renderDropdownList(query);
1272
+
1273
+ if (query) {
1274
+ searchIcon.style.display = 'none';
1275
+ clearSearch.classList.add('visible');
1276
+ } else {
1277
+ searchIcon.style.display = 'block';
1278
+ clearSearch.classList.remove('visible');
1279
+ }
1280
+ }, 300);
1281
  });
1282
 
1283
  clearSearch.addEventListener('click', () => {
 
1298
  }
1299
  });
1300
 
1301
+ // Keyboard navigation for dropdown
1302
+ document.addEventListener('keydown', (e) => {
1303
+ if (e.key === 'Escape' && isDropdownOpen) {
1304
+ toggleDropdown(true);
1305
+ }
1306
+ });
1307
+
1308
  // Button event listeners
1309
  grayscaleBtn.addEventListener('click', toggleGrayscale);
1310
  downloadCurrentBtn.addEventListener('click', downloadCurrentPDF);
 
1318
  console.error(`Image element with ID "${imgElementId}" not found.`);
1319
  return;
1320
  }
1321
+
1322
+ // Use a placeholder if no src is provided
1323
+ if (!src) {
1324
+ imgElement.src = defaultSrc;
1325
+ imgElement.alt = workerName || type;
1326
+ return;
1327
+ }
1328
+
1329
+ imgElement.src = src;
1330
  imgElement.alt = workerName || type;
1331
  imgElement.crossOrigin = "anonymous";
1332
+
1333
  imgElement.onerror = function() {
1334
+ console.warn(`Failed to load image for ${type}: ${src}`);
1335
+ this.src = defaultSrc;
1336
+ this.alt = 'No Image';
1337
+ };
1338
+
1339
+ imgElement.onload = function() {
1340
+ console.log(`Successfully loaded image for ${type}: ${workerName}`);
1341
  };
1342
  }
1343
 
 
1565
  document.getElementById('alienReferenceReceipt').innerHTML = worker.alienReferenceNumber || 'N/A';
1566
  document.getElementById('personalIDReceipt').innerHTML = worker.personalID || 'N/A';
1567
  setImageSrc('profilePic', worker.profileImage, defaultProfileSrc, worker.englishName, 'profile picture');
1568
+
1569
+ const currentDomain = window.location.origin;
1570
+ const workerPageUrl = `${currentDomain}/worker.html?id=${encodeURIComponent(worker.requestNumber || '')}`;
1571
+ const receiptUrl = `${currentDomain}/pdf.html?id=${encodeURIComponent(worker.requestNumber || '')}`;
1572
 
1573
+ setImageSrc('qrCode', `https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=${encodeURIComponent(workerPageUrl)}`, defaultQrCodeSrc, worker.englishName, 'work permit QR code');
1574
+ setImageSrc('receiptQrCode', `https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=${encodeURIComponent(receiptUrl)}`, defaultReceiptQrCodeSrc, worker.englishName, 'receipt QR code');
 
 
 
 
1575
 
1576
  const timestamp = getCurrentTimestamp();
1577
  const workPermitTimestamp = `เอกสารอิเล็กทรอนิกส์ฉบับนี้ถูกสร้างจากระบบอนุญาตทำงานคนต่างด้าวที่มีสถานะการทำงานไม่ถูกต้องตามกฎหมาย ตามมติคณะรัฐมนตรีเมื่อวันที่ 24 กันยายน 2567<br>โดยกรมการจัดหางาน กระทรวงแรงงาน<br>พิมพ์เอกสาร วันที่ 10/04/2568 ${timestamp} `;
 
1606
  const percent = total > 0 ? Math.round((current / total) * 100) : 0;
1607
  progressFill.style.width = `${percent}%`;
1608
  progressText.textContent = `กำลังดาวน์โหลด ${current}/${total} (${percent}%)`;
1609
+
1610
+ // Update progress bar color based on percentage
1611
+ if (percent >= 90) {
1612
+ progressFill.style.backgroundColor = '#10b981'; // Green
1613
+ } else if (percent >= 50) {
1614
+ progressFill.style.backgroundColor = '#f59e0b'; // Yellow
1615
+ } else {
1616
+ progressFill.style.backgroundColor = '#3b82f6'; // Blue
1617
+ }
1618
  } else {
1619
  progressContainer.classList.remove('visible');
1620
+ progressFill.style.width = '0%';
1621
  }
1622
  }
1623
 
 
1630
  grayscaleBtn.disabled = disabled;
1631
  }
1632
 
1633
+ // Optimized PDF download function
1634
+ async function downloadPDF(workerItem, isBatchDownload = false) {
1635
+ try {
1636
+ // Store current state
1637
+ const originalIndex = currentIndex;
1638
+ const originalGrayscale = isGrayscaleEnabled;
1639
+
1640
+ // If this is a different worker, temporarily display it
1641
+ if (workerItem.requestNumber !== allWorkerData[currentIndex]?.requestNumber) {
1642
+ const tempIndex = allWorkerData.findIndex(w => w.requestNumber === workerItem.requestNumber);
1643
+ if (tempIndex !== -1) {
1644
+ displayWorkerData(tempIndex);
1645
+ await new Promise(resolve => setTimeout(resolve, 500)); // Wait for data to load
1646
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1647
  }
1648
+
1649
+ // Randomize overlays
1650
+ randomizeBothOverlays();
1651
+ await new Promise(resolve => setTimeout(resolve, 200)); // Wait for overlays
1652
+
1653
+ // Hide controls during export
1654
+ const controls = document.querySelector('.controls-container');
1655
+ if (controls) controls.style.display = 'none';
1656
+
1657
+ // Ensure all images are loaded
1658
+ const images = document.querySelectorAll('img');
1659
+ await Promise.all(Array.from(images).map(img => {
1660
+ return new Promise(resolve => {
1661
+ if (img.complete && img.naturalWidth > 0) {
1662
+ resolve();
1663
+ } else {
1664
+ const timer = setTimeout(() => {
1665
+ console.warn(`Timeout loading image: ${img.src}`);
1666
+ resolve();
1667
+ }, 5000);
1668
+
1669
+ img.onload = () => {
1670
+ clearTimeout(timer);
1671
+ resolve();
1672
+ };
1673
+ img.onerror = () => {
1674
+ clearTimeout(timer);
1675
+ console.error(`Failed to load image: ${img.src}`);
1676
+ resolve();
1677
+ };
1678
+ }
1679
+ });
1680
+ }));
1681
+
1682
+ // Generate filename
1683
+ const cleanName = (workerItem.englishName || workerItem.thaiName || 'unknown')
1684
+ .replace(/[^a-zA-Z0-9ก-๙]/g, '_')
1685
+ .substring(0, 20);
1686
+ const reqNum = workerItem.requestNumber || '0000';
1687
+ const last4 = reqNum.slice(-4);
1688
+ const filename = `${last4}_${cleanName}_work_permit.pdf`;
1689
+
1690
+ // Create container for PDF export
1691
+ const container = document.createElement('div');
1692
+ container.className = 'pdf-export-container';
1693
+ container.style.width = '892px';
1694
+ container.style.height = '2522px';
1695
+ container.style.position = 'absolute';
1696
+ container.style.left = '-9999px';
1697
+ container.style.top = '0';
1698
+
1699
+ // Clone the pages
1700
+ const page1Clone = document.getElementById('page1-div').cloneNode(true);
1701
+ const page2Clone = document.getElementById('page2-div').cloneNode(true);
1702
+
1703
+ // Apply styles for PDF
1704
+ [page1Clone, page2Clone].forEach(page => {
1705
+ page.style.margin = '0';
1706
+ page.style.padding = '0';
1707
+ page.style.border = 'none';
1708
+
1709
+ // Force grayscale for all images
1710
+ page.querySelectorAll('img').forEach(img => {
1711
+ img.style.filter = 'grayscale(100%)';
1712
+ img.style.webkitFilter = 'grayscale(100%)';
1713
+ });
1714
+ });
1715
+
1716
+ container.appendChild(page1Clone);
1717
+ container.appendChild(page2Clone);
1718
+ document.body.appendChild(container);
1719
+
1720
+ // Wait a moment for rendering
1721
+ await new Promise(resolve => setTimeout(resolve, 300));
1722
+
1723
+ // Generate PDF
1724
+ const opt = {
1725
+ margin: [0, 0, 0, 0],
1726
+ filename: filename,
1727
+ image: {
1728
+ type: 'jpeg',
1729
+ quality: 0.95
1730
+ },
1731
+ html2canvas: {
1732
+ scale: 2,
1733
+ useCORS: true,
1734
+ allowTaint: true,
1735
+ backgroundColor: '#ffffff',
1736
+ logging: false,
1737
+ width: 892,
1738
+ height: 2522,
1739
+ scrollX: 0,
1740
+ scrollY: 0,
1741
+ windowWidth: 892
1742
+ },
1743
+ jsPDF: {
1744
+ unit: 'px',
1745
+ format: [892, 1261],
1746
+ orientation: 'portrait',
1747
+ compress: true
1748
+ }
1749
+ };
1750
+
1751
+ await html2pdf().set(opt).from(container).save();
1752
+
1753
+ // Clean up
1754
+ document.body.removeChild(container);
1755
+ if (controls) controls.style.display = 'block';
1756
+
1757
+ // Restore original state
1758
+ if (originalIndex !== currentIndex) {
1759
+ displayWorkerData(originalIndex);
1760
+ }
1761
+
1762
+ // Restore grayscale setting
1763
+ if (originalGrayscale !== isGrayscaleEnabled) {
1764
+ toggleGrayscale();
1765
+ }
1766
+
1767
+ return true;
1768
+
1769
+ } catch (error) {
1770
+ console.error('Error generating PDF:', error);
1771
+
1772
+ // Ensure controls are visible
1773
+ const controls = document.querySelector('.controls-container');
1774
+ if (controls) controls.style.display = 'block';
1775
+
1776
+ // Restore original worker view
1777
+ if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1778
+ displayWorkerData(currentIndex);
1779
+ }
1780
+
1781
+ if (!isBatchDownload) {
1782
+ alert('เกิดข้อผิดพลาดในการสร้าง PDF กรุณาลองใหม่อีกครั้ง');
1783
+ }
1784
+
1785
+ return false;
1786
  }
1787
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1788
 
1789
  async function downloadCurrentPDF() {
1790
  if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1791
  setButtonsDisabled(true);
1792
  try {
1793
+ await downloadPDF(allWorkerData[currentIndex], false);
1794
  } catch (error) {
1795
  alert('เกิดข้อผิดพลาดในการสร้าง PDF กรุณาลองใหม่อีกครั้ง');
1796
  }
 
1807
  }
1808
 
1809
  const selectedArray = Array.from(selectedWorkers).sort((a, b) => a - b);
1810
+ const confirmDownload = confirm(`คุณต้องการดาวน์โหลดใบอนุญาตทำงานสำหรับพนักงาน ${selectedArray.length} รายการใช่หรือไม่?\n\nกระบวนการนี้อาจใช้เวลาสักครู่`);
1811
  if (!confirmDownload) return;
1812
 
1813
  setButtonsDisabled(true);
1814
  showProgress(true, 0, selectedArray.length);
1815
 
1816
+ let successCount = 0;
1817
+ let failCount = 0;
1818
+ const failList = [];
1819
+
1820
  for (let i = 0; i < selectedArray.length; i++) {
1821
  const workerIndex = selectedArray[i];
1822
+ const worker = allWorkerData[workerIndex];
1823
+
1824
+ showProgress(true, i + 1, selectedArray.length);
1825
+
1826
  try {
1827
+ const success = await downloadPDF(worker, true);
1828
+ if (success) {
1829
+ successCount++;
1830
+ } else {
1831
+ failCount++;
1832
+ failList.push(worker.requestNumber || `index: ${workerIndex}`);
1833
+ }
1834
+
1835
+ // Add delay between downloads to prevent browser overload
1836
+ if (i < selectedArray.length - 1) {
1837
+ await new Promise(resolve => setTimeout(resolve, 1000));
1838
+ }
1839
+
1840
  } catch (error) {
1841
  console.error(`Error downloading PDF for worker at index ${workerIndex}:`, error);
1842
+ failCount++;
1843
+ failList.push(worker.requestNumber || `index: ${workerIndex}`);
1844
  }
1845
  }
1846
 
1847
  showProgress(false);
1848
  setButtonsDisabled(false);
 
1849
 
1850
+ // Display results
1851
+ let resultMessage = `ดาวน์โหลดสำเร็จ ${successCount} รายการ`;
1852
+ if (failCount > 0) {
1853
+ resultMessage += `\nล้มเหลว ${failCount} รายการ`;
1854
+ if (failList.length > 0) {
1855
+ resultMessage += `\nรายการที่ล้มเหลว: ${failList.slice(0, 5).join(', ')}${failList.length > 5 ? '...' : ''}`;
1856
+ }
1857
+ }
1858
+
1859
+ alert(resultMessage);
1860
+
1861
+ // Restore original view
1862
  if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1863
  displayWorkerData(currentIndex);
1864
  }
 
1870
  return;
1871
  }
1872
 
1873
+ const confirmDownload = confirm(`คุณต้องการดาวน์โหลดใบอนุญาตทำงานสำหรับพนักงานทั้งหมด ${allWorkerData.length} รายการใช่หรือไม่?\n\nกระบวนการนี้อาจใช้เวลานาน กรุณาอย่าปิดเบราว์เซอร์`);
1874
  if (!confirmDownload) return;
1875
 
1876
  setButtonsDisabled(true);
1877
  showProgress(true, 0, allWorkerData.length);
1878
 
1879
+ let successCount = 0;
1880
+ let failCount = 0;
1881
+ const failList = [];
1882
+
1883
  for (let i = 0; i < allWorkerData.length; i++) {
1884
+ showProgress(true, i + 1, allWorkerData.length);
1885
+
1886
  try {
1887
+ const success = await downloadPDF(allWorkerData[i], true);
1888
+ if (success) {
1889
+ successCount++;
1890
+ } else {
1891
+ failCount++;
1892
+ failList.push(allWorkerData[i].requestNumber || `index: ${i}`);
1893
+ }
1894
+
1895
+ // Add delay between downloads
1896
+ if (i < allWorkerData.length - 1) {
1897
+ await new Promise(resolve => setTimeout(resolve, 800));
1898
+ }
1899
+
1900
  } catch (error) {
1901
  console.error(`Error downloading PDF for worker at index ${i}:`, error);
1902
+ failCount++;
1903
+ failList.push(allWorkerData[i].requestNumber || `index: ${i}`);
1904
  }
1905
  }
1906
 
1907
  showProgress(false);
1908
  setButtonsDisabled(false);
 
1909
 
1910
+ // Display results
1911
+ let resultMessage = `ดาวน์โหลดสำเร็จ ${successCount} รายการจากทั้งหมด ${allWorkerData.length} รายการ`;
1912
+ if (failCount > 0) {
1913
+ resultMessage += `\nล้มเหลว ${failCount} รายการ`;
1914
+ if (failList.length > 0) {
1915
+ resultMessage += `\nรายการที่ล้มเหลว (5 รายการแรก): ${failList.slice(0, 5).join(', ')}`;
1916
+ }
1917
+ }
1918
+
1919
+ alert(resultMessage);
1920
+
1921
+ // Restore original view
1922
  if (currentIndex >= 0 && currentIndex < allWorkerData.length) {
1923
  displayWorkerData(currentIndex);
1924
  } else if (allWorkerData.length > 0) {
 
1930
  try {
1931
  await loadWorkerData();
1932
  randomizeBothOverlays();
1933
+
1934
+ // Add some CSS for highlighting
1935
+ const style = document.createElement('style');
1936
+ style.textContent = `
1937
+ .highlight {
1938
+ background-color: #ffeb3b;
1939
+ color: #000;
1940
+ font-weight: bold;
1941
+ padding: 0 2px;
1942
+ border-radius: 2px;
1943
+ }
1944
+ .dropdown-item-meta {
1945
+ font-size: 12px;
1946
+ color: #666;
1947
+ margin-top: 2px;
1948
+ }
1949
+ .dropdown-item-meta span {
1950
+ margin-right: 4px;
1951
+ }
1952
+ #selectedCount.has-selection {
1953
+ color: #3b82f6;
1954
+ font-weight: bold;
1955
+ }
1956
+ `;
1957
+ document.head.appendChild(style);
1958
+
1959
  } catch (e) {
1960
  console.error('Error initializing application:', e);
1961
  }
1962
  };
1963
+ </script>
1964
  </body>
1965
  </html>