| <!DOCTYPE html> |
| <html xmlns="http://www.w3.org/1999/xhtml" lang="th" xml:lang="th"> |
| <head> |
| <title>journey</title> |
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"/> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script> |
| <style type="text/css"> |
| @font-face { |
| font-family: 'THSarabunNew'; |
| src: url('fonts/THSarabunNew.ttf') format('truetype'); |
| font-weight: normal; |
| font-style: normal; |
| unicode-range: U+0E00-0E7F, U+0020-007F, U+00A0-00FF; |
| font-display: swap; |
| } |
| |
| @font-face { |
| font-family: 'THSarabunNew'; |
| src: url('fonts/THSarabunNew Bold.ttf') format('truetype'); |
| font-weight: bold; |
| font-style: normal; |
| unicode-range: U+0E00-0E7F, U+0020-007F, U+00A0-00FF; |
| font-display: swap; |
| } |
| |
| * { |
| font-family: 'THSarabunNew', sans-serif !important; |
| } |
| |
| .ft00 { font-size: 19px; color: #000000; } |
| .ft01 { font-size: 25px; color: #000000; } |
| .ft02 { font-size: 22px; color: #000000; } |
| .ft03 { font-size: 22px; color: #000000; } |
| .ft06 { font-size: 15px; line-height: 18px; color: #000000; } |
| |
| |
| @media print { |
| body { |
| filter: grayscale(100%); |
| } |
| .controls-container { display: none !important; } |
| body { margin: 0 !important; padding: 0 !important; } |
| .page-div { width: 100% !important; |
| height: auto !important; |
| margin: 0 !important; |
| padding: 0 !important; |
| page-break-after: always; } |
| .page-div:last-child { page-break-after: avoid; } |
| .overlay-frame { display: none !important; } |
| } |
| |
| .page-div { |
| position: relative; |
| width: 892px; |
| height: 1261px; |
| background: none; |
| margin: 10px auto; |
| } |
| .page-div:last-child { |
| margin-bottom: 0; |
| } |
| |
| .controls-container { |
| position: fixed; |
| bottom: 16px; |
| left: 16px; |
| background: rgba(255, 255, 255, 0.95); |
| padding: 16px; |
| border-radius: 12px; |
| width: 340px; |
| max-width: calc(100vw - 32px); |
| z-index: 1000; |
| display: flex; |
| flex-direction: column; |
| gap: 10px; |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); |
| backdrop-filter: blur(10px); |
| border: 1px solid rgba(255, 255, 255, 0.3); |
| } |
| |
| |
| .custom-dropdown { |
| position: relative; |
| width: 100%; |
| } |
| |
| .dropdown-header { |
| display: flex; |
| align-items: center; |
| gap: 10px; |
| padding: 10px 12px; |
| background: white; |
| border: 2px solid #e2e8f0; |
| border-radius: 8px; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| min-height: 48px; |
| } |
| |
| .dropdown-header:hover { |
| border-color: #3b82f6; |
| } |
| |
| .dropdown-header:focus-within { |
| border-color: #3b82f6; |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2); |
| } |
| |
| .dropdown-header img { |
| width: 32px; |
| height: 32px; |
| border-radius: 50%; |
| object-fit: cover; |
| flex-shrink: 0; |
| } |
| |
| .dropdown-header-text { |
| flex: 1; |
| font-size: 15px; |
| color: #1e293b; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .dropdown-arrow { |
| width: 20px; |
| height: 20px; |
| transition: transform 0.3s ease; |
| flex-shrink: 0; |
| } |
| |
| .dropdown-arrow.open { |
| transform: rotate(180deg); |
| } |
| |
| .dropdown-menu { |
| position: absolute; |
| bottom: calc(100% + 8px); |
| left: 0; |
| right: 0; |
| background: white; |
| border-radius: 12px; |
| box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2); |
| opacity: 0; |
| visibility: hidden; |
| transform: translateY(10px); |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
| z-index: 1001; |
| overflow: hidden; |
| max-height: 400px; |
| display: flex; |
| flex-direction: column; |
| } |
| |
| .dropdown-menu.open { |
| opacity: 1; |
| visibility: visible; |
| transform: translateY(0); |
| } |
| |
| .search-container { |
| padding: 12px; |
| border-bottom: 1px solid #e2e8f0; |
| background: #f8fafc; |
| position: sticky; |
| top: 0; |
| z-index: 10; |
| } |
| |
| .search-input { |
| width: 100%; |
| padding: 10px 40px 10px 12px; |
| border: 2px solid #e2e8f0; |
| border-radius: 8px; |
| font-size: 15px; |
| outline: none; |
| transition: all 0.2s ease; |
| background: white; |
| } |
| |
| .search-input:focus { |
| border-color: #3b82f6; |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15); |
| } |
| |
| .search-input::placeholder { |
| color: #94a3b8; |
| } |
| |
| .search-wrapper { |
| position: relative; |
| } |
| |
| .search-icon { |
| position: absolute; |
| right: 12px; |
| top: 50%; |
| transform: translateY(-50%); |
| width: 20px; |
| height: 20px; |
| color: #94a3b8; |
| pointer-events: none; |
| } |
| |
| .clear-search { |
| position: absolute; |
| right: 12px; |
| top: 50%; |
| transform: translateY(-50%); |
| width: 24px; |
| height: 24px; |
| background: #ef4444; |
| color: white; |
| border: none; |
| border-radius: 50%; |
| cursor: pointer; |
| display: none; |
| align-items: center; |
| justify-content: center; |
| font-size: 14px; |
| font-weight: bold; |
| transition: background 0.2s; |
| } |
| |
| .clear-search:hover { |
| background: #dc2626; |
| } |
| |
| .clear-search.visible { |
| display: flex; |
| } |
| |
| .dropdown-list { |
| overflow-y: auto; |
| max-height: 280px; |
| overscroll-behavior: contain; |
| } |
| |
| .dropdown-list::-webkit-scrollbar { |
| width: 8px; |
| } |
| |
| .dropdown-list::-webkit-scrollbar-track { |
| background: #f1f5f9; |
| } |
| |
| .dropdown-list::-webkit-scrollbar-thumb { |
| background: #cbd5e1; |
| border-radius: 4px; |
| } |
| |
| .dropdown-list::-webkit-scrollbar-thumb:hover { |
| background: #94a3b8; |
| } |
| |
| .dropdown-item { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| padding: 12px 16px; |
| cursor: pointer; |
| transition: all 0.15s ease; |
| border-bottom: 1px solid #f1f5f9; |
| } |
| |
| .dropdown-item:last-child { |
| border-bottom: none; |
| } |
| |
| .dropdown-item:hover { |
| background: #f0f9ff; |
| } |
| |
| .dropdown-item.selected { |
| background: #eff6ff; |
| } |
| |
| .dropdown-item.active { |
| background: #dbeafe; |
| } |
| |
| .dropdown-item-image { |
| width: 40px; |
| height: 40px; |
| border-radius: 50%; |
| object-fit: cover; |
| flex-shrink: 0; |
| border: 2px solid #e2e8f0; |
| } |
| |
| .dropdown-item-content { |
| flex: 1; |
| min-width: 0; |
| } |
| |
| .dropdown-item-title { |
| font-size: 14px; |
| font-weight: 600; |
| color: #1e293b; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .dropdown-item-subtitle { |
| font-size: 12px; |
| color: #64748b; |
| white-space: nowrap; |
| overflow: hidden; |
| text-overflow: ellipsis; |
| } |
| |
| .dropdown-item-checkbox { |
| flex-shrink: 0; |
| } |
| |
| .custom-checkbox { |
| width: 22px; |
| height: 22px; |
| border: 2px solid #cbd5e1; |
| border-radius: 6px; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| background: white; |
| } |
| |
| .custom-checkbox:hover { |
| border-color: #3b82f6; |
| } |
| |
| .custom-checkbox.checked { |
| background: #3b82f6; |
| border-color: #3b82f6; |
| } |
| |
| .custom-checkbox svg { |
| width: 14px; |
| height: 14px; |
| color: white; |
| opacity: 0; |
| transform: scale(0.5); |
| transition: all 0.2s ease; |
| } |
| |
| .custom-checkbox.checked svg { |
| opacity: 1; |
| transform: scale(1); |
| } |
| |
| .dropdown-actions { |
| padding: 12px; |
| border-top: 1px solid #e2e8f0; |
| background: #f8fafc; |
| display: flex; |
| gap: 8px; |
| flex-wrap: wrap; |
| } |
| |
| .action-btn { |
| flex: 1; |
| min-width: 100px; |
| padding: 10px 16px; |
| border: none; |
| border-radius: 8px; |
| font-size: 14px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 6px; |
| } |
| |
| .action-btn:active { |
| transform: scale(0.98); |
| } |
| |
| .btn-select-all { |
| background: #e2e8f0; |
| color: #475569; |
| } |
| |
| .btn-select-all:hover { |
| background: #cbd5e1; |
| } |
| |
| .btn-clear-selection { |
| background: #fee2e2; |
| color: #dc2626; |
| } |
| |
| .btn-clear-selection:hover { |
| background: #fecaca; |
| } |
| |
| .selected-count { |
| padding: 8px 12px; |
| background: #dbeafe; |
| color: #1d4ed8; |
| border-radius: 8px; |
| font-size: 13px; |
| font-weight: 600; |
| text-align: center; |
| } |
| |
| .no-results { |
| padding: 40px 20px; |
| text-align: center; |
| color: #94a3b8; |
| } |
| |
| .no-results svg { |
| width: 48px; |
| height: 48px; |
| margin: 0 auto 12px; |
| opacity: 0.5; |
| } |
| |
| |
| .control-btn { |
| padding: 12px 16px; |
| border-radius: 8px; |
| font-size: 15px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| border: none; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 8px; |
| min-height: 48px; |
| } |
| |
| .control-btn:active { |
| transform: scale(0.98); |
| } |
| |
| .control-btn:disabled { |
| opacity: 0.5; |
| cursor: not-allowed; |
| } |
| |
| .btn-grayscale { |
| background: #6b7280; |
| color: white; |
| } |
| |
| .btn-grayscale:hover:not(:disabled) { |
| background: #4b5563; |
| } |
| |
| .btn-download-current { |
| background: white; |
| color: #1e293b; |
| border: 2px solid #e2e8f0; |
| } |
| |
| .btn-download-current:hover:not(:disabled) { |
| background: #f8fafc; |
| border-color: #cbd5e1; |
| } |
| |
| .btn-download-selected { |
| background: #3b82f6; |
| color: white; |
| } |
| |
| .btn-download-selected:hover:not(:disabled) { |
| background: #2563eb; |
| } |
| |
| .btn-download-all { |
| background: #10b981; |
| color: white; |
| } |
| |
| .btn-download-all:hover:not(:disabled) { |
| background: #059669; |
| } |
| |
| .btn-randomize { |
| background: #8b5cf6; |
| color: white; |
| } |
| |
| .btn-randomize:hover:not(:disabled) { |
| background: #7c3aed; |
| } |
| |
| .overlay-frame { |
| width: 300px; |
| height: 320px; |
| border: none; |
| position: absolute; |
| overflow: hidden; |
| z-index: 50; |
| background: transparent; |
| } |
| |
| .overlay-image-container { |
| width: 260px; |
| height: 150px; |
| background: transparent; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| position: absolute; |
| transition: all 0.8s cubic-bezier(0.4, 0, 0.2, 1); |
| transform-origin: center; |
| } |
| |
| .overlay-image-container img { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| object-fit: cover; |
| } |
| |
| #page1-overlay { |
| top: 700px; |
| right: 320px; |
| } |
| #page2-overlay { |
| top: 1000px; |
| left: 500px; |
| } |
| |
| |
| @media (max-width: 480px) { |
| .controls-container { |
| width: calc(100% - 32px); |
| left: 16px; |
| bottom: 16px; |
| padding: 12px; |
| } |
| |
| .dropdown-menu { |
| max-height: 350px; |
| } |
| |
| .dropdown-list { |
| max-height: 200px; |
| } |
| |
| .overlay-frame { |
| width: 250px; |
| height: 260px; |
| } |
| .overlay-image-container { |
| width: 200px; |
| height: 120px; |
| } |
| .stamp-text { |
| font-size: 12px; |
| } |
| #page1-overlay { |
| top: 700px; |
| right: 320px; |
| } |
| #page2-overlay { |
| top: 1000px; |
| left: 500px; |
| } |
| .page-div { |
| width: 100%; |
| height: auto; |
| margin: 10px 0; |
| } |
| |
| .control-btn { |
| font-size: 14px; |
| padding: 10px 12px; |
| } |
| } |
| |
| |
| .loading-spinner { |
| width: 20px; |
| height: 20px; |
| border: 2px solid transparent; |
| border-top-color: currentColor; |
| border-radius: 50%; |
| animation: spin 0.8s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| |
| .progress-container { |
| display: none; |
| padding: 12px; |
| background: #f0fdf4; |
| border-radius: 8px; |
| border: 1px solid #bbf7d0; |
| } |
| |
| .progress-container.visible { |
| display: block; |
| } |
| |
| .progress-bar { |
| height: 8px; |
| background: #e2e8f0; |
| border-radius: 4px; |
| overflow: hidden; |
| margin-bottom: 8px; |
| } |
| |
| .progress-fill { |
| height: 100%; |
| background: linear-gradient(90deg, #10b981, #34d399); |
| border-radius: 4px; |
| transition: width 0.3s ease; |
| } |
| |
| .progress-text { |
| font-size: 13px; |
| color: #166534; |
| text-align: center; |
| } |
| |
| |
| .highlight { |
| background: #fef08a; |
| padding: 0 2px; |
| border-radius: 2px; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100"> |
| <div class="controls-container" role="region" aria-label="Controls"> |
| |
| <div class="custom-dropdown" id="customDropdown"> |
| <div class="dropdown-header" |
| id="dropdownHeader" |
| tabindex="0" |
| role="combobox" |
| aria-expanded="false" |
| aria-haspopup="listbox" |
| aria-label="เลือกพนักงาน"> |
| <img id="dropdownProfilePic" src="https://via.placeholder.com/32?text=..." alt="Profile"/> |
| <span class="dropdown-header-text" id="selectedText">กรุณาเลือกพนักงาน</span> |
| <svg class="dropdown-arrow" id="dropdownArrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="6,9 12,15 18,9"></polyline> |
| </svg> |
| </div> |
| <div class="dropdown-menu" id="dropdownMenu" role="listbox" aria-label="รายชื่อพนักงาน"> |
| <div class="search-container"> |
| <div class="search-wrapper"> |
| <input type="text" |
| class="search-input" |
| id="searchInput" |
| placeholder="ค้นหา ชื่อ, เลขที่, หรือ ID..." |
| aria-label="ค้นหาพนักงาน" |
| autocomplete="off"/> |
| <svg class="search-icon" id="searchIcon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="11" cy="11" r="8"></circle> |
| <line x1="21" y1="21" x2="16.65" y2="16.65"></line> |
| </svg> |
| <button class="clear-search" id="clearSearch" aria-label="ล้างการค้นหา">×</button> |
| </div> |
| </div> |
| <div class="dropdown-list" id="dropdownList"> |
| |
| </div> |
| <div class="dropdown-actions"> |
| <div class="selected-count" id="selectedCount">เลือกแล้ว 0 รายการ</div> |
| <button class="action-btn btn-select-all" id="selectAllBtn" aria-label="เลือกทั้งหมด"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="9,11 12,14 22,4"></polyline> |
| <path d="M21,12v7a2,2,0,0,1-2,2H5a2,2,0,0,1-2-2V5A2,2,0,0,1,5,3h11"></path> |
| </svg> |
| เลือกทั้งหมด |
| </button> |
| <button class="action-btn btn-clear-selection" id="clearSelectionBtn" aria-label="ล้างการเลือก"> |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <line x1="18" y1="6" x2="6" y2="18"></line> |
| <line x1="6" y1="6" x2="18" y2="18"></line> |
| </svg> |
| ล้าง |
| </button> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="progress-container" id="progressContainer"> |
| <div class="progress-bar"> |
| <div class="progress-fill" id="progressFill" style="width: 0%"></div> |
| </div> |
| <div class="progress-text" id="progressText">กำลังดาวน์โหลด...</div> |
| </div> |
|
|
| |
| <button type="button" id="grayscaleBtn" class="control-btn btn-grayscale"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <path d="M12 2a10 10 0 0 1 0 20"></path> |
| </svg> |
| Grayscale: ปิด |
| </button> |
|
|
| <button id="downloadCurrentBtn" class="control-btn btn-download-current"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
| <polyline points="7,10 12,15 17,10"></polyline> |
| <line x1="12" y1="15" x2="12" y2="3"></line> |
| </svg> |
| ดาวน์โหลด PDF ปัจจุบัน |
| </button> |
|
|
| <button id="downloadSelectedBtn" class="control-btn btn-download-selected" disabled> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
| <polyline points="7,10 12,15 17,10"></polyline> |
| <line x1="12" y1="15" x2="12" y2="3"></line> |
| </svg> |
| ดาวน์โหลด PDF ที่เลือก (0) |
| </button> |
|
|
| <button id="downloadAllBtn" class="control-btn btn-download-all"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
| <polyline points="7,10 12,15 17,10"></polyline> |
| <line x1="12" y1="15" x2="12" y2="3"></line> |
| </svg> |
| ดาวน์โหลด PDF ทั้งหมด |
| </button> |
|
|
| <button id="randomizeBtn" class="control-btn btn-randomize"> |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <polyline points="16,3 21,3 21,8"></polyline> |
| <line x1="4" y1="20" x2="21" y2="3"></line> |
| <polyline points="21,16 21,21 16,21"></polyline> |
| <line x1="15" y1="15" x2="21" y2="21"></line> |
| <line x1="4" y1="4" x2="9" y2="9"></line> |
| </svg> |
| สุ่มตำแหน่งภาพ |
| </button> |
| </div> |
|
|
| |
| <div id="page1-div" class="page-div"> |
| <img width="892" height="1261" src="bg3.svg" alt="Background"/> |
| <div id="overlayContainer1" class="overlay-frame" style="width: 300px; height: 200px; position: absolute; top: 700px; right: 320px;"></div> |
| <img class="absolute top-[46px] left-[725px] w-[110px] h-[138px] object-cover z-[100]" id="profilePic" src="https://via.placeholder.com/110x138?text=Loading" alt="Profile Picture"/> |
| <img class="absolute top-[977px] left-[762.5px] w-[69px] h-[69px] object-cover" id="qrCode" src="https://via.placeholder.com/69?text=Loading" alt="QR Code"/> |
| <p class="absolute top-[32px] left-[134px] whitespace-nowrap text-[21px] font-bold">ทะเบียนใบอนุญาตทำงานของคนต่างด้าวตามมติคณะรัฐมนตรี เมื่อวันที่ 24 กันยายน 2567</p> |
| <p class="absolute top-[57px] left-[134px] whitespace-nowrap text-[21px] font-bold">เอกสารฉบับนี้ใช้แทนใบอนุญาตทำงาน</p> |
| <p class="absolute top-[87px] left-[158px] whitespace-nowrap text-[15px]">เลขรับที่ (No.)</p> |
| <p class="absolute top-[87px] left-[224px] whitespace-nowrap text-[14px]">:</p> |
| <p class="absolute top-[87px] left-[238px] whitespace-nowrap text-[14px] font-bold" id="requestNumber">xxxxxxx</p> |
| <p class="absolute top-[87px] left-[417px] whitespace-nowrap text-[15px]">วันที่อนุมัติ (Date)</p> |
| <p class="absolute top-[87px] left-[500px] whitespace-nowrap text-[14px]">:</p> |
| <p class="absolute top-[87px] left-[514px] whitespace-nowrap text-[15px] font-bold">06 มีนาคม 2568</p> |
| <p class="absolute top-[114px] left-[62px] whitespace-nowrap text-[15px]">ชื่อคนต่างด้าว (Name of Applicant)</p> |
| <p class="absolute top-[114px] left-[224px] whitespace-nowrap text-[14px]">:</p> |
| <p class="absolute top-[114px] left-[238px] whitespace-nowrap text-[14px] font-bold" id="englishName">xxxxxxxxxxxxx</p> |
| <p class="absolute top-[140px] left-[94px] whitespace-nowrap text-[15px]">เจ้าหน้าที่ (Name of Officer)</p> |
| <p class="absolute top-[140px] left-[224px] whitespace-nowrap text-[14px]">:</p> |
| <p class="absolute top-[140px] left-[238px] whitespace-nowrap text-[15px] font-bold">นายสมมาตร อนันต์ธราทรัพย์</p> |
| <p class="absolute top-[140px] left-[440px] whitespace-nowrap text-[15px]">นายทะเบียน</p> |
| <p class="absolute top-[140px] left-[500px] whitespace-nowrap text-[14px]">:</p> |
| <p class="absolute top-[167px] left-[238px] whitespace-nowrap text-[15px]">จัดหางานจังหวัดระยอง</p> |
| <p class="absolute top-[163px] left-[449px] whitespace-nowrap text-[15px]">(Registrar)</p> |
| <p class="absolute top-[163px] left-[546px] whitespace-nowrap text-[15px]">นายสมชาย มรกตศรีวรรณ</p> |
| <p class="absolute top-[182px] left-[555px] whitespace-nowrap text-[15px]">อธิบดีกรมการจัดหางาน</p> |
| <p class="absolute top-[201px] left-[577px] whitespace-nowrap text-[15px]">นายทะเบียน</p> |
| <p class="absolute top-[227px] left-[171px] whitespace-nowrap text-[16px] font-bold">ลงเลขรับและชำระค่ายื่นแบบคำขอ (REGISTERING APPLICATION FORM AND PAYING APPLICATION FEE)</p> |
| <p class="absolute top-[254px] left-[55px] whitespace-nowrap text-[15px] font-bold leading-[21px]">ข้อมูลคนต่างด้าว</p> |
| <p class="absolute top-[278px] left-[55px] whitespace-nowrap text-[14px]">สถานะใบอนุญาต</p> |
| <p class="absolute top-[278px] left-[199px] whitespace-nowrap text-[14px]">: อนุมัติ (รอพิมพ์บัตร)</p> |
| <p class="absolute top-[278px] left-[440px] whitespace-nowrap text-[14px]">ออกให้ ณ จังหวัด</p> |
| <p class="absolute top-[278px] left-[586px] whitespace-nowrap text-[14px]">: สำนักงานจัดหางานจังหวัดระยอง</p> |
| <p class="absolute top-[300px] left-[55px] whitespace-nowrap text-[14px]">เลขประจำตัวคนต่างด้าว</p> |
| <p class="absolute top-[300px] left-[199px] whitespace-nowrap text-[14px]" id="personalID">: 6685490000472</p> |
| <p class="absolute top-[300px] left-[440px] whitespace-nowrap text-[14px]">ใบอนุญาตทำงานเลขที่</p> |
| <p class="absolute top-[300px] left-[586px] whitespace-nowrap text-[14px]" id="workPermitNumber">: 5400689000472</p> |
| <p class="absolute top-[321px] left-[55px] whitespace-nowrap text-[14px]">ชื่อภาษาไทย</p> |
| <p class="absolute top-[321px] left-[199px] whitespace-nowrap text-[14px]" id="thaiName">: นาง เมย์ เท็ท โช</p> |
| <p class="absolute top-[321px] left-[440px] whitespace-nowrap text-[14px]">ชื่อภาษาอังกฤษ</p> |
| <p class="absolute top-[321px] left-[586px] whitespace-nowrap text-[14px]">:</p> |
| <p class="absolute top-[321px] left-[592px] whitespace-nowrap text-[14px]" id="englishNameDuplicate">MRS. MAY THET CHO</p> |
| <p class="absolute top-[343px] left-[55px] whitespace-nowrap text-[14px]">วัน/เดือน/ปี (พ.ศ.) เกิด</p> |
| <p class="absolute top-[343px] left-[199px] whitespace-nowrap text-[14px]" id="birthDate">: xx/xx/xx</p> |
| <p class="absolute top-[343px] left-[440px] whitespace-nowrap text-[14px]">อายุ (ปี)</p> |
| <p class="absolute top-[343px] left-[586px] whitespace-nowrap text-[14px]" id="age">: xx</p> |
| <p class="absolute top-[365px] left-[55px] whitespace-nowrap text-[14px]">สัญชาติ</p> |
| <p class="absolute top-[365px] left-[199px] whitespace-nowrap text-[14px]" id="nationality">: เมียนมา</p> |
| <p class="absolute top-[365px] left-[440px] whitespace-nowrap text-[14px]">สถานภาพ</p> |
| <p class="absolute top-[365px] left-[586px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[386px] left-[55px] whitespace-nowrap text-[14px]">ชื่อ-สกุล บิดา</p> |
| <p class="absolute top-[386px] left-[199px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[386px] left-[440px] whitespace-nowrap text-[14px]">ชื่อ-สกุล มารดา</p> |
| <p class="absolute top-[386px] left-[586px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[408px] left-[55px] whitespace-nowrap text-[14px]">เลขอ้างอิงคนต่างด้าว</p> |
| <p class="absolute top-[408px] left-[199px] whitespace-nowrap text-[14px]" id="alienReferenceNumber">: xxxxxxxxxxxxx</p> |
| <p class="absolute top-[429px] left-[55px] whitespace-nowrap text-[14px]">ที่อยู่อาศัย</p> |
| <p class="absolute top-[429px] left-[199px] whitespace-nowrap text-[14px]">: 36/6 หมู่ที่ 8 ตำบลมาบข่า อำเภอนิคมพัฒนา จังหวัดระยอง 21180</p> |
| <p class="absolute top-[449px] left-[55px] whitespace-nowrap text-[15px] font-bold leading-[21px]">ข้อมูลหนังสือเดินทาง และข้อมูลการตรวจลงตรา</p> |
| <p class="absolute top-[469px] left-[55px] whitespace-nowrap text-[14px]">เลขที่หนังสือเดินทาง</p> |
| <p class="absolute top-[472px] left-[199px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[472px] left-[440px] whitespace-nowrap text-[14px]">ประเภทหนังสือเดินทาง</p> |
| <p class="absolute top-[472px] left-[586px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[494px] left-[55px] whitespace-nowrap text-[14px]">สถานที่ออกหนังสือเดินทาง</p> |
| <p class="absolute top-[494px] left-[199px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[494px] left-[440px] whitespace-nowrap text-[14px]">ประเทศที่ออกหนังสือเดินทาง</p> |
| <p class="absolute top-[494px] left-[586px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[516px] left-[55px] whitespace-nowrap text-[14px]">วันที่ออกหนังสือเดินทาง</p> |
| <p class="absolute top-[516px] left-[199px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[516px] left-[440px] whitespace-nowrap text-[14px]">วันหมดอายุ</p> |
| <p class="absolute top-[516px] left-[586px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[537px] left-[55px] whitespace-nowrap text-[14px]">เลขที่ตรวจลงตรา</p> |
| <p class="absolute top-[537px] left-[199px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[559px] left-[55px] whitespace-nowrap text-[14px]">ออกให้วันที่</p> |
| <p class="absolute top-[559px] left-[199px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[559px] left-[440px] whitespace-nowrap text-[14px]">ใช้ได้ถึงวันที่</p> |
| <p class="absolute top-[559px] left-[586px] whitespace-nowrap text-[14px]">: -</p> |
| <p class="absolute top-[578px] left-[55px] whitespace-nowrap text-[15px] font-bold leading-[21px]">ข้อมูลนายจ้าง/สถานประกอบการ</p> |
| <p class="absolute top-[598px] left-[55px] whitespace-nowrap text-[14px]">เลขประจำตัวนายจ้าง</p> |
| <p class="absolute top-[602px] left-[199px] whitespace-nowrap text-[14px]">: 0415567000061</p> |
| <p class="absolute top-[602px] left-[440px] whitespace-nowrap text-[14px]">ชื่อนายจ้าง/สถานประกอบการ</p> |
| <p class="absolute top-[602px] left-[586px] whitespace-nowrap text-[14px]">: บริษัท บาน กง เอ็นจิเนียริ่ง จำกัด</p> |
| <p class="absolute top-[623px] left-[55px] whitespace-nowrap text-[14px]">ประเภทกิจการ</p> |
| <p class="absolute top-[623px] left-[199px] whitespace-nowrap text-[14px]">: BT04 - กิจการก่อสร้าง</p> |
| <p class="absolute top-[645px] left-[55px] whitespace-nowrap text-[14px]">ที่ตั้งสำนักงาน</p> |
| <p class="absolute top-[645px] left-[199px] whitespace-nowrap text-[14px]">: 102 หมู่ที่ 8 ถนนอุดรธานี-ขอนแก่น ตำบลโนนสูง อำเภอเมืองอุดรธานี จังหวัดอุดรธานี 41000</p> |
| <p class="absolute top-[666px] left-[55px] whitespace-nowrap text-[15px] font-bold leading-[21px]">ข้อมูลการทำงาน</p> |
| <p class="absolute top-[684px] left-[55px] whitespace-nowrap text-[14px]">ทำงานในตำแหน่ง</p> |
| <p class="absolute top-[688px] left-[199px] whitespace-nowrap text-[14px]">: กรรมกร</p> |
| <p class="absolute top-[688px] left-[440px] whitespace-nowrap text-[14px]">ลักษณะงาน</p> |
| <p class="absolute top-[688px] left-[586px] whitespace-nowrap text-[14px]">: กรรมกร (กิจการก่อสร้าง)</p> |
| <p class="absolute top-[710px] left-[55px] whitespace-nowrap text-[14px]">สถานที่ทำงาน</p> |
| <p class="absolute top-[710px] left-[199px] whitespace-nowrap text-[14px]">: 36/6 หมู่ที่ 8 ตำบลมาบข่า อำเภอนิคมพัฒนา จังหวัดระยอง 21180</p> |
| <p class="absolute top-[731px] left-[55px] whitespace-nowrap text-[14px]">อนุญาตให้ทำงานถึงวันที่</p> |
| <p class="absolute top-[731px] left-[199px] whitespace-nowrap text-[14px]">: 31 มีนาคม 2569</p> |
| <p class="absolute top-[755px] left-[55px] whitespace-nowrap text-[15px] font-bold leading-[21px]">ข้อมูลสิทธิการรักษาพยาบาล</p> |
| <p class="absolute top-[775px] left-[55px] whitespace-nowrap text-[15px] leading-[21px]">ประกันสังคม</p> |
| <p class="absolute top-[796px] left-[55px] whitespace-nowrap text-[15px] leading-[21px]">ประกันสุขภาพ สิ้นสุดวันที่ 30/09/2025</p> |
| <p class="absolute top-[825px] left-[55px] whitespace-nowrap text-[14px] font-bold leading-[19px]">เงื่อนไข</p> |
| <p class="absolute top-[841px] left-[85px] whitespace-nowrap text-[14px] leading-[19px]">คนต่างด้าวจะต้องทำประกันสุขภาพตลอดระยะเวลาการอนุญาตให้ทำงาน หากปรากฎว่าระยะเวลาการทำประกันสุขภาพสิ้นสุดลง ก่อนระยะเวลาการอนุญาตให้ทำงาน</p> |
| <p class="absolute top-[857px] left-[55px] whitespace-nowrap text-[14px] leading-[19px]">นายทะเบียนจะเพิกถอนใบอนุญาตทำงาน ซึ่งมีผลให้การอนุญาตให้อยู่ในราชอาณาจักรสิ้นสุดลง</p> |
| <p class="absolute top-[885px] left-[55px] whitespace-nowrap text-[14px] font-bold leading-[19px]">คำเตือน</p> |
| <p class="absolute top-[905px] left-[85px] whitespace-nowrap text-[14px] leading-[19px]">เมื่อได้รับอนุญาตให้ทำงานแล้วคนต่างด้าวต้องดำเนินการดังต่อไปนี้ มิเช่นนั้น การอนุญาตให้ทำงานและการอนุญาตให้อยู่ในราชอาณาจักรของคนต่างด้าวจะสิ้นสุดลง</p> |
| <p class="absolute top-[922px] left-[55px] whitespace-nowrap text-[14px] leading-[19px]">1. จัดเก็บข้อมูลอัตลักษณ์บุคคล ภายในวันที่ 28 มิถุนายน 2568</p> |
| <p class="absolute top-[939px] left-[55px] whitespace-nowrap text-[14px] leading-[19px]">2. จัดทำหรือปรับปรุงทะเบียนประวัติตามกฎหมายว่าด้วยการทะเบียนราษฎร ภายในวันที่ 31 มีนาคม 2569</p> |
| <p class="absolute top-[1000px] left-[55px] text-[14px] leading-[19px]" id="timestamp">Loading...</p> |
| </div> |
|
|
| |
| <div id="page2-div" class="page-div"> |
| <img width="892" height="1261" src="bg2.svg" alt="Receipt Background"/> |
| <div id="overlayContainer2" class="overlay-frame" style="position: absolute; top: 1000px; left: 500px;"></div> |
| <img class="absolute top-[925px] left-[120px] w-[90px] h-[90px] object-cover" id="receiptQrCode" src="https://via.placeholder.com/90?text=Loading" alt="Receipt QR Code"/> |
| <p style="position:absolute;top:147px;left:86px;white-space:nowrap" class="ft00">กรมการจัดหางาน</p> |
| <p style="position:absolute;top:170px;left:88px;white-space:nowrap" class="ft00">กระทรวงแรงงาน</p> |
| <p style="position:absolute;top:90px;left:397px;white-space:nowrap" class="ft01"><b>ใบเสร็จรับเงิน</b></p> |
| <p style="position:absolute;top:120px;left:418px;white-space:nowrap" class="ft01"><b>ต้นฉบับ</b></p> |
| <p style="position:absolute;top:60px;left:598px;white-space:nowrap" class="ft00">เลขที่</p> |
| <p style="position:absolute;top:60px;left:640px;white-space:nowrap" class="ft00" id="receiptNumberReceipt">xxxxxxx</p> |
| <p style="position:absolute;top:149px;left:582px;white-space:nowrap" class="ft00">ที่ทำการ   สำนักบริหารแรงงานต่างด้าว</p> |
| <p style="position:absolute;top:188px;left:602px;white-space:nowrap" class="ft00">วันที่   19 มีนาคม 2568</p> |
| <p style="position:absolute;top:227px;left:540px;white-space:nowrap" class="ft00">เลขที่ใบชำระเงิน  </p> |
| <p style="position:absolute;top:227px;left:640px;white-space:nowrap" class="ft00" id="paymentNumberReceipt">IV680329/002308</p> |
| <p style="position:absolute;top:271px;left:60px;white-space:nowrap" class="ft00">เลขรับคำขอที่</p> |
| <p style="position:absolute;top:271px;left:180px;white-space:nowrap" class="ft00" id="requestNumberReceipt">xxxxxxx</p> |
| <p style="position:absolute;top:310px;left:60px;white-space:nowrap" class="ft00">ชื่อผู้ชำระเงิน</p> |
| <p style="position:absolute;top:310px;left:180px;white-space:nowrap" class="ft00" id="payerNameReceipt">xxxxxxxxxxxxx</p> |
| <p style="position:absolute;top:310px;left:471px;white-space:nowrap" class="ft00">สัญชาติ</p> |
| <p style="position:absolute;top:310px;left:520px;white-space:nowrap" class="ft00" id="nationalityReceipt">เมียนมา</p> |
| <p style="position:absolute;top:354px;left:60px;white-space:nowrap" class="ft00">เลขอ้างอิงคนต่างด้าว</p> |
| <p style="position:absolute;top:354px;left:180px;white-space:nowrap" class="ft00" id="alienReferenceReceipt">xxxxxxxxxxxxx</p> |
| <p style="position:absolute;top:354px;left:432px;white-space:nowrap" class="ft00">หมายเลขประจำตัวคนต่างด้าว</p> |
| <p style="position:absolute;top:354px;left:640px;white-space:nowrap" class="ft00" id="personalIDReceipt">xxxxxxxxxxxxx</p> |
| <p style="position:absolute;top:399px;left:60px;white-space:nowrap" class="ft00">ชื่อนายจ้าง / สถานประกอบการ   บริษัท บาน กง เอ็นจิเนียริ่ง จำกัด</p> |
| <p style="position:absolute;top:438px;left:60px;white-space:nowrap" class="ft00">เลขประจำตัวนายจ้าง</p> |
| <p style="position:absolute;top:437px;left:233px;white-space:nowrap" class="ft00">  0415567000061</p> |
| <p style="position:absolute;top:526px;left:345px;white-space:nowrap" class="ft02"><b>รายการ</b></p> |
| <p style="position:absolute;top:526px;left:688px;white-space:nowrap" class="ft02"><b>จำนวนเงิน</b></p> |
| <p style="position:absolute;top:572px;left:118px;white-space:nowrap" class="ft03">1. ค่าธรรมเนียมในการยื่นคำขอ ฉบับละ 100 บาท</p> |
| <p style="position:absolute;top:572px;left:736px;white-space:nowrap" class="ft03">100.00</p> |
| <p style="position:absolute;top:616px;left:118px;white-space:nowrap" class="ft03">2. ค่าธรรมเนียมใบอนุญาตทำงาน</p> |
| <p style="position:absolute;top:616px;left:736px;white-space:nowrap" class="ft03">900.00</p> |
| <p style="position:absolute;top:694px;left:97px;white-space:nowrap" class="ft03"> </p> |
| <p style="position:absolute;top:694px;left:648px;white-space:nowrap" class="ft03"> </p> |
| <p style="position:absolute;top:772px;left:174px;white-space:nowrap" class="ft02"><b>รวมเป็นเงินทั้งสิ้น (บาท)</b></p> |
| <p style="position:absolute;top:799px;left:188px;white-space:nowrap" class="ft02"><b>( หนึ่งพันบาทถ้วน )</b></p> |
| <p style="position:absolute;top:786px;left:385px;white-space:nowrap" class="ft03"> </p> |
| <p style="position:absolute;top:774px;left:722px;white-space:nowrap" class="ft02"><b>1,000.00</b></p> |
| <p style="position:absolute;top:894px;left:94px;white-space:nowrap" class="ft00">ได้รับเงินไว้เป็นการถูกต้องแล้ว</p> |
| <p style="position:absolute;top:977px;left:481px;white-space:nowrap" class="ft00">(ลงชื่อ)</p> |
| <p style="position:absolute;top:977px;left:564px;white-space:nowrap" class="ft00">นางสาวอารีวรรณ โพธิ์นิ่มแดง</p> |
| <p style="position:absolute;top:977px;left:762px;white-space:nowrap" class="ft00">(ผู้รับเงิน)</p> |
| <p style="position:absolute;top:1017px;left:473px;white-space:nowrap" class="ft00">ตำแหน่ง</p> |
| <p style="position:absolute;top:1016px;left:562px;white-space:nowrap" class="ft00">นักวิชาการแรงงานชำนาญการ</p> |
| <p style="position:absolute;top:1133px;left:55px;white-space:nowrap" class="ft06" id="receiptTimestamp">Loading...</p> |
| </div> |
|
|
| <script type="text/javascript"> |
| let allWorkerData = []; |
| let currentIndex = 0; |
| let isGrayscaleEnabled = false; |
| let selectedWorkers = new Set(); |
| let isDropdownOpen = false; |
| let filteredData = []; |
| |
| |
| const dropdownHeader = document.getElementById('dropdownHeader'); |
| const dropdownMenu = document.getElementById('dropdownMenu'); |
| const dropdownArrow = document.getElementById('dropdownArrow'); |
| const dropdownList = document.getElementById('dropdownList'); |
| const searchInput = document.getElementById('searchInput'); |
| const searchIcon = document.getElementById('searchIcon'); |
| const clearSearch = document.getElementById('clearSearch'); |
| const selectedText = document.getElementById('selectedText'); |
| const selectedCountEl = document.getElementById('selectedCount'); |
| const selectAllBtn = document.getElementById('selectAllBtn'); |
| const clearSelectionBtn = document.getElementById('clearSelectionBtn'); |
| const downloadSelectedBtn = document.getElementById('downloadSelectedBtn'); |
| const downloadCurrentBtn = document.getElementById('downloadCurrentBtn'); |
| const downloadAllBtn = document.getElementById('downloadAllBtn'); |
| const grayscaleBtn = document.getElementById('grayscaleBtn'); |
| const randomizeBtn = document.getElementById('randomizeBtn'); |
| const progressContainer = document.getElementById('progressContainer'); |
| const progressFill = document.getElementById('progressFill'); |
| const progressText = document.getElementById('progressText'); |
| |
| |
| function normalizeText(text) { |
| if (!text) return ''; |
| return text.toString().toLowerCase().trim() |
| .replace(/[่้๊๋์]/g, '') |
| .replace(/\s+/g, ' ') |
| .normalize('NFD').replace(/[\u0300-\u036f]/g, ''); |
| } |
| |
| |
| function searchWorkers(query) { |
| const normalizedQuery = normalizeText(query); |
| if (!normalizedQuery || normalizedQuery.length < 1) { |
| filteredData = [...allWorkerData]; |
| return filteredData; |
| } |
| |
| |
| const searchTerms = normalizedQuery.split(/\s+/).filter(term => term.length > 0); |
| |
| filteredData = allWorkerData.filter(worker => { |
| |
| const searchFields = [ |
| worker.requestNumber, |
| worker.englishName, |
| worker.thaiName, |
| worker.personalID, |
| worker.workPermitNumber, |
| worker.alienReferenceNumber, |
| worker.nationality, |
| worker.age, |
| worker.birthDate |
| ]; |
| |
| |
| const normalizedFields = searchFields.map(field => normalizeText(field)); |
| |
| |
| const phraseMatch = normalizedFields.some(field => |
| field.includes(normalizedQuery) |
| ); |
| |
| |
| const allTermsMatch = searchTerms.every(term => |
| normalizedFields.some(field => field.includes(term)) |
| ); |
| |
| |
| const anyTermMatch = searchTerms.some(term => |
| normalizedFields.some(field => field.includes(term)) |
| ); |
| |
| |
| let partialMatchScore = 0; |
| searchTerms.forEach(term => { |
| normalizedFields.forEach(field => { |
| if (field.includes(term)) { |
| partialMatchScore++; |
| } |
| }); |
| }); |
| |
| |
| return phraseMatch || allTermsMatch || anyTermMatch || partialMatchScore > 0; |
| }); |
| |
| |
| filteredData.sort((a, b) => { |
| const aScore = calculateRelevanceScore(a, normalizedQuery, searchTerms); |
| const bScore = calculateRelevanceScore(b, normalizedQuery, searchTerms); |
| return bScore - aScore; |
| }); |
| |
| return filteredData; |
| } |
| |
| |
| function calculateRelevanceScore(worker, query, terms) { |
| let score = 0; |
| const fields = [ |
| { value: worker.requestNumber, weight: 5 }, |
| { value: worker.englishName, weight: 4 }, |
| { value: worker.thaiName, weight: 4 }, |
| { value: worker.personalID, weight: 3 }, |
| { value: worker.workPermitNumber, weight: 3 }, |
| { value: worker.alienReferenceNumber, weight: 2 }, |
| { value: worker.nationality, weight: 1 } |
| ]; |
| |
| fields.forEach(field => { |
| const normalizedField = normalizeText(field.value); |
| |
| |
| if (normalizedField === query) { |
| score += 10 * field.weight; |
| } |
| |
| |
| if (normalizedField.includes(query)) { |
| score += 5 * field.weight; |
| } |
| |
| |
| if (terms.every(term => normalizedField.includes(term))) { |
| score += 3 * field.weight; |
| } |
| |
| |
| terms.forEach(term => { |
| if (normalizedField.includes(term)) { |
| score += field.weight; |
| } |
| }); |
| }); |
| |
| return score; |
| } |
| |
| |
| function highlightText(text, query) { |
| if (!text || !query) return text || ''; |
| |
| const normalizedText = normalizeText(text); |
| const normalizedQuery = normalizeText(query); |
| |
| if (!normalizedQuery || !normalizedText.includes(normalizedQuery)) { |
| return text; |
| } |
| |
| |
| const searchTerms = normalizedQuery.split(/\s+/); |
| let highlightedText = text; |
| |
| searchTerms.forEach(term => { |
| if (term.length > 0) { |
| |
| const regex = new RegExp(`(${escapeRegex(term)})`, 'gi'); |
| highlightedText = highlightedText.replace(regex, '<span class="highlight">$1</span>'); |
| } |
| }); |
| |
| return highlightedText; |
| } |
| |
| function escapeRegex(string) { |
| return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); |
| } |
| |
| |
| function toggleDropdown(forceClose = false) { |
| if (forceClose || isDropdownOpen) { |
| isDropdownOpen = false; |
| dropdownMenu.classList.remove('open'); |
| dropdownArrow.classList.remove('open'); |
| dropdownHeader.setAttribute('aria-expanded', 'false'); |
| } else { |
| isDropdownOpen = true; |
| dropdownMenu.classList.add('open'); |
| dropdownArrow.classList.add('open'); |
| dropdownHeader.setAttribute('aria-expanded', 'true'); |
| searchInput.focus(); |
| renderDropdownList(); |
| } |
| } |
| |
| |
| function renderDropdownList(query = '') { |
| const workers = searchWorkers(query); |
| |
| if (workers.length === 0) { |
| dropdownList.innerHTML = ` |
| <div class="no-results"> |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="11" cy="11" r="8"></circle> |
| <line x1="21" y1="21" x2="16.65" y2="16.65"></line> |
| </svg> |
| <p>ไม่พบผลลัพธ์สำหรับ "${query}"</p> |
| <small>ลองค้นหาโดยใช้เลขที่ขออนุญาต, ชื่อ, เลขบัตรประชาชน, หรือเลขใบอนุญาตทำงาน</small> |
| </div> |
| `; |
| return; |
| } |
| |
| dropdownList.innerHTML = workers.map((worker, idx) => { |
| const originalIndex = allWorkerData.findIndex(w => w.requestNumber === worker.requestNumber); |
| const isChecked = selectedWorkers.has(originalIndex); |
| const isActive = originalIndex === currentIndex; |
| |
| const displayName = query ? highlightText(worker.englishName || worker.thaiName || 'N/A', query) : (worker.englishName || worker.thaiName || 'N/A'); |
| const displayId = query ? highlightText(worker.requestNumber || 'N/A', query) : (worker.requestNumber || 'N/A'); |
| |
| return ` |
| <div class="dropdown-item ${isChecked ? 'selected' : ''} ${isActive ? 'active' : ''}" |
| data-index="${originalIndex}" |
| role="option" |
| aria-selected="${isActive}" |
| tabindex="0"> |
| <img class="dropdown-item-image" |
| src="${worker.profileImage || 'https://via.placeholder.com/40?text=N/A'}" |
| alt="${worker.englishName || 'Worker'}" |
| loading="lazy" |
| onerror="this.src='https://via.placeholder.com/40?text=N/A'"/> |
| <div class="dropdown-item-content"> |
| <div class="dropdown-item-title">${displayName}</div> |
| <div class="dropdown-item-subtitle">เลขที่: ${displayId}</div> |
| <div class="dropdown-item-meta"> |
| <span>${worker.nationality || 'N/A'}</span> |
| <span>•</span> |
| <span>${worker.personalID ? worker.personalID.slice(-4) : 'N/A'}</span> |
| </div> |
| </div> |
| <div class="dropdown-item-checkbox" onclick="event.stopPropagation(); toggleWorkerSelection(${originalIndex})"> |
| <div class="custom-checkbox ${isChecked ? 'checked' : ''}" role="checkbox" aria-checked="${isChecked}"> |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"> |
| <polyline points="20,6 9,17 4,12"></polyline> |
| </svg> |
| </div> |
| </div> |
| </div> |
| `; |
| }).join(''); |
| |
| |
| dropdownList.querySelectorAll('.dropdown-item').forEach(item => { |
| item.addEventListener('click', (e) => { |
| if (!e.target.closest('.dropdown-item-checkbox')) { |
| const index = parseInt(item.dataset.index); |
| selectWorker(index); |
| } |
| }); |
| |
| |
| item.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' || e.key === ' ') { |
| e.preventDefault(); |
| const index = parseInt(item.dataset.index); |
| if (e.shiftKey) { |
| toggleWorkerSelection(index); |
| } else { |
| selectWorker(index); |
| } |
| } |
| }); |
| }); |
| } |
| |
| |
| function selectWorker(index) { |
| if (index >= 0 && index < allWorkerData.length) { |
| currentIndex = index; |
| displayWorkerData(index); |
| updateDropdownHeader(); |
| randomizeBothOverlays(); |
| toggleDropdown(true); |
| } |
| } |
| |
| |
| function toggleWorkerSelection(index) { |
| if (index >= 0 && index < allWorkerData.length) { |
| if (selectedWorkers.has(index)) { |
| selectedWorkers.delete(index); |
| } else { |
| selectedWorkers.add(index); |
| } |
| updateSelectionUI(); |
| renderDropdownList(searchInput.value); |
| } |
| } |
| |
| |
| function updateSelectionUI() { |
| const count = selectedWorkers.size; |
| selectedCountEl.textContent = `เลือกแล้ว ${count} รายการ`; |
| selectedCountEl.className = count > 0 ? 'has-selection' : ''; |
| downloadSelectedBtn.disabled = count === 0; |
| downloadSelectedBtn.innerHTML = ` |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path> |
| <polyline points="7,10 12,15 17,10"></polyline> |
| <line x1="12" y1="15" x2="12" y2="3"></line> |
| </svg> |
| ดาวน์โหลด PDF ที่เลือก (${count}) |
| `; |
| } |
| |
| |
| function updateDropdownHeader() { |
| if (currentIndex >= 0 && currentIndex < allWorkerData.length) { |
| const worker = allWorkerData[currentIndex]; |
| selectedText.textContent = `${worker.requestNumber || 'N/A'} - ${worker.englishName || worker.thaiName || 'N/A'}`; |
| setImageSrc('dropdownProfilePic', worker.profileImage, 'https://via.placeholder.com/32?text=N/A', worker.englishName, 'dropdown profile'); |
| } else { |
| selectedText.textContent = 'กรุณาเลือกพนักงาน'; |
| document.getElementById('dropdownProfilePic').src = 'https://via.placeholder.com/32?text=...'; |
| } |
| } |
| |
| |
| function selectAllWorkers() { |
| const currentFiltered = searchWorkers(searchInput.value); |
| currentFiltered.forEach(worker => { |
| const idx = allWorkerData.findIndex(w => w.requestNumber === worker.requestNumber); |
| if (idx !== -1) selectedWorkers.add(idx); |
| }); |
| updateSelectionUI(); |
| renderDropdownList(searchInput.value); |
| } |
| |
| |
| function clearAllSelections() { |
| selectedWorkers.clear(); |
| updateSelectionUI(); |
| renderDropdownList(searchInput.value); |
| } |
| |
| |
| dropdownHeader.addEventListener('click', () => toggleDropdown()); |
| dropdownHeader.addEventListener('keydown', (e) => { |
| if (e.key === 'Enter' || e.key === ' ') { |
| e.preventDefault(); |
| toggleDropdown(); |
| } |
| }); |
| |
| searchInput.addEventListener('input', (e) => { |
| const query = e.target.value; |
| |
| |
| clearTimeout(searchInput.debounceTimer); |
| searchInput.debounceTimer = setTimeout(() => { |
| renderDropdownList(query); |
| |
| if (query) { |
| searchIcon.style.display = 'none'; |
| clearSearch.classList.add('visible'); |
| } else { |
| searchIcon.style.display = 'block'; |
| clearSearch.classList.remove('visible'); |
| } |
| }, 300); |
| }); |
| |
| clearSearch.addEventListener('click', () => { |
| searchInput.value = ''; |
| searchIcon.style.display = 'block'; |
| clearSearch.classList.remove('visible'); |
| renderDropdownList(''); |
| searchInput.focus(); |
| }); |
| |
| selectAllBtn.addEventListener('click', selectAllWorkers); |
| clearSelectionBtn.addEventListener('click', clearAllSelections); |
| |
| |
| document.addEventListener('click', (e) => { |
| if (!e.target.closest('.custom-dropdown')) { |
| toggleDropdown(true); |
| } |
| }); |
| |
| |
| document.addEventListener('keydown', (e) => { |
| if (e.key === 'Escape' && isDropdownOpen) { |
| toggleDropdown(true); |
| } |
| }); |
| |
| |
| grayscaleBtn.addEventListener('click', toggleGrayscale); |
| downloadCurrentBtn.addEventListener('click', downloadCurrentPDF); |
| downloadSelectedBtn.addEventListener('click', downloadSelectedPDFs); |
| downloadAllBtn.addEventListener('click', downloadAllPDFs); |
| randomizeBtn.addEventListener('click', randomizeBothOverlays); |
| |
| function setImageSrc(imgElementId, src, defaultSrc, workerName, type) { |
| const imgElement = document.getElementById(imgElementId); |
| if (!imgElement) { |
| console.error(`Image element with ID "${imgElementId}" not found.`); |
| return; |
| } |
| |
| |
| if (!src) { |
| imgElement.src = defaultSrc; |
| imgElement.alt = workerName || type; |
| return; |
| } |
| |
| imgElement.src = src; |
| imgElement.alt = workerName || type; |
| imgElement.crossOrigin = "anonymous"; |
| |
| imgElement.onerror = function() { |
| console.warn(`Failed to load image for ${type}: ${src}`); |
| this.src = defaultSrc; |
| this.alt = 'No Image'; |
| }; |
| |
| imgElement.onload = function() { |
| console.log(`Successfully loaded image for ${type}: ${workerName}`); |
| }; |
| } |
| |
| function getCurrentTimestamp() { |
| const now = new Date(); |
| return now.toLocaleString('th-TH', { |
| hour: '2-digit', |
| minute: '2-digit', |
| hour12: false |
| }).replace(',', ' น.') + ' น.'; |
| } |
| |
| function toggleGrayscale() { |
| isGrayscaleEnabled = !isGrayscaleEnabled; |
| document.body.style.filter = isGrayscaleEnabled ? 'grayscale(100%)' : 'grayscale(0%)'; |
| grayscaleBtn.innerHTML = ` |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <path d="M12 2a10 10 0 0 1 0 20"></path> |
| </svg> |
| Grayscale: ${isGrayscaleEnabled ? 'เปิด' : 'ปิด'} |
| `; |
| } |
| |
| function jitter(base, maxDelta = 5) { |
| return base + (Math.random() * 2 - 1) * maxDelta; |
| } |
| |
| const borImages = ['bor01.png', 'bor02.png', 'bor03.png', 'bor04.png']; |
| let borCounter = 0; |
| |
| function randomizeOverlayPosition(containerId, startIdx, useSetA = false, borIndex = 0, opacity = 1, refPos = null) { |
| const container = document.getElementById(containerId); |
| if (!container) return []; |
| |
| const setA = ['a1.png','a2.png','a3.png','a4.png','a5.png','a6.png']; |
| const setB = ['b1.png','b2.png','b3.png','b4.png','b5.png','b6.png']; |
| const imageFiles = useSetA ? setA : setB; |
| |
| const borSrc = borImages[borCounter % borImages.length]; |
| borCounter++; |
| |
| container.innerHTML = ''; |
| |
| const imgs = []; |
| for (let i = 0; i < 3; i++) { |
| const img = document.createElement('img'); |
| img.src = imageFiles[(startIdx + i) % 6]; |
| img.style.zIndex = [1,2,3][i]; |
| img.style.position = 'absolute'; |
| container.appendChild(img); |
| imgs.push(img); |
| } |
| |
| const borImg = document.createElement('img'); |
| borImg.src = borSrc; |
| borImg.style.opacity = opacity; |
| borImg.style.zIndex = 0; |
| borImg.style.position = 'absolute'; |
| container.insertBefore(borImg, container.firstChild); |
| |
| Promise.all([ |
| ...imgs.map(i => new Promise(r => { i.onload = r; if (i.complete) r(); })), |
| new Promise(r => { borImg.onload = r; if (borImg.complete) r(); }) |
| ]).then(() => { |
| const w = container.offsetWidth; |
| const h = container.offsetHeight; |
| |
| const positions = imgs.map((img, i) => { |
| const imgW = img.offsetWidth; |
| const imgH = img.offsetHeight; |
| const maxX = w - imgW; |
| const maxY = h - imgH; |
| |
| const baseX = refPos && refPos[i] ? refPos[i].x : Math.random() * maxX; |
| const baseY = refPos && refPos[i] ? refPos[i].y : Math.random() * maxY; |
| const x = jitter(baseX); |
| const y = jitter(baseY); |
| |
| const rotation = (Math.random() - 0.5) * 10 + (startIdx + i) * 0.3; |
| let transform = `rotate(${rotation}deg)`; |
| |
| if (parseInt(img.style.zIndex) === 3) { |
| const scale = 1 + (Math.random() - 0.5) * 0.3; |
| transform += ` scale(${scale})`; |
| } |
| |
| img.style.left = `${Math.max(0, Math.min(x, maxX))}px`; |
| img.style.top = `${Math.max(0, Math.min(y, maxY))}px`; |
| img.style.transform = transform; |
| |
| if (i === 0) { |
| borImg.style.left = img.style.left; |
| borImg.style.top = img.style.top; |
| borImg.style.transform = transform; |
| } |
| |
| return { x: parseFloat(img.style.left), y: parseFloat(img.style.top) }; |
| }); |
| |
| container._lastPositions = positions; |
| }); |
| |
| return container._lastPositions || []; |
| } |
| |
| function randomizeBothOverlays() { |
| const useSetA = Math.random() < 0.5; |
| const useReverse = Math.random() < 0.5; |
| const start1 = useReverse ? 3 : 0; |
| const start2 = useReverse ? 0 : 3; |
| |
| const borIndex1 = borCounter % 4; |
| const borIndex2 = (borCounter + 1) % 4; |
| borCounter++; |
| |
| const baseOpacity = 0.75 + Math.random() * 0.15; |
| const opacity1 = Math.min(1, baseOpacity + 0.05); |
| const opacity2 = Math.max(0.6, baseOpacity - 0.05); |
| |
| const ref = randomizeOverlayPosition('overlayContainer1', start1, useSetA, borIndex1, opacity1); |
| randomizeOverlayPosition('overlayContainer2', start2, useSetA, borIndex2, opacity2, ref); |
| } |
| |
| function loadWorkerData() { |
| const urlParams = new URLSearchParams(window.location.search); |
| const requestNumberFromUrl = urlParams.get('id'); |
| |
| |
| document.getElementById('requestNumber').innerHTML = '<b>Loading data...</b>'; |
| document.getElementById('englishName').innerHTML = '<b>Loading data...</b>'; |
| document.getElementById('englishNameDuplicate').innerHTML = 'Loading data...'; |
| document.getElementById('thaiName').innerHTML = ': Loading data...'; |
| document.getElementById('personalID').innerHTML = ': Loading data...'; |
| document.getElementById('workPermitNumber').innerHTML = ': Loading data...'; |
| document.getElementById('alienReferenceNumber').innerHTML = ': Loading data...'; |
| document.getElementById('nationality').innerHTML = ': Loading data...'; |
| document.getElementById('age').innerHTML = ': Loading data...'; |
| document.getElementById('birthDate').innerHTML = ': Loading data...'; |
| document.getElementById('timestamp').innerHTML = 'Loading data...'; |
| document.getElementById('requestNumberReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('receiptNumberReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('paymentNumberReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('payerNameReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('nationalityReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('alienReferenceReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('personalIDReceipt').innerHTML = 'Loading data...'; |
| document.getElementById('receiptTimestamp').innerHTML = 'Loading data...'; |
| |
| fetch('combined-data.json') |
| .then(response => { |
| if (!response.ok) throw new Error('Failed to load combined-data.json'); |
| return response.json(); |
| }) |
| .then(data => { |
| if (!Array.isArray(data) || data.length === 0) throw new Error('Data is empty or not a valid array.'); |
| allWorkerData = data; |
| filteredData = [...data]; |
| |
| currentIndex = requestNumberFromUrl |
| ? allWorkerData.findIndex(w => w.requestNumber && w.requestNumber.toString() === requestNumberFromUrl.toString()) |
| : 0; |
| if (currentIndex === -1) currentIndex = 0; |
| |
| displayWorkerData(currentIndex); |
| updateDropdownHeader(); |
| renderDropdownList(); |
| updateSelectionUI(); |
| }) |
| .catch(error => { |
| console.error('Error loading JSON:', error); |
| showError(error.message); |
| }); |
| } |
| |
| function showError(message) { |
| const errorDiv = document.createElement('div'); |
| errorDiv.className = 'text-red-500 text-center mt-5 p-2 bg-red-100 border border-red-500 rounded'; |
| errorDiv.innerHTML = `<p>Error loading: ${message}</p>`; |
| document.body.appendChild(errorDiv); |
| clearWorkerData(); |
| setImageSrc('profilePic', null, 'https://via.placeholder.com/110x138?text=Error', 'N/A', 'profile picture'); |
| setImageSrc('qrCode', null, 'https://via.placeholder.com/69?text=Error', 'N/A', 'work permit QR code'); |
| setImageSrc('receiptQrCode', null, 'https://via.placeholder.com/90?text=Error', 'N/A', 'receipt QR code'); |
| setImageSrc('dropdownProfilePic', null, 'https://via.placeholder.com/32?text=Error', 'N/A', 'dropdown profile picture'); |
| const timestamp = getCurrentTimestamp(); |
| const workPermitTimestamp = `เอกสารอิเล็กทรอนิกส์ฉบับนี้ถูกสร้างจากระบบอนุญาตทำงานคนต่างด้าวที่มีสถานะการทำงานไม่ถูกต้องตามกฎหมาย ตามมติคณะรัฐมนตรีเมื่อวันที่ 24 กันยายน 2567<br>โดยกรมการจัดหางาน กระทรวงแรงงาน<br>พิมพ์เอกสาร วันที่ 10/04/2568 ${timestamp} `; |
| document.getElementById('timestamp').innerHTML = workPermitTimestamp; |
| document.getElementById('receiptTimestamp').innerHTML = workPermitTimestamp; |
| } |
| |
| function displayWorkerData(index) { |
| const defaultProfileSrc = 'https://via.placeholder.com/110x138?text=No+Image'; |
| const defaultQrCodeSrc = 'https://via.placeholder.com/69?text=No+QR'; |
| const defaultReceiptQrCodeSrc = 'https://via.placeholder.com/90?text=No+QR'; |
| |
| if (!allWorkerData || !Array.isArray(allWorkerData) || index < 0 || index >= allWorkerData.length) { |
| clearWorkerData(); |
| setImageSrc('profilePic', null, defaultProfileSrc, 'N/A', 'profile picture'); |
| setImageSrc('qrCode', null, defaultQrCodeSrc, 'N/A', 'work permit QR code'); |
| setImageSrc('receiptQrCode', null, defaultReceiptQrCodeSrc, 'N/A', 'receipt QR code'); |
| const timestamp = getCurrentTimestamp(); |
| const workPermitTimestamp = `เอกสารอิเล็กทรอนิกส์ฉบับนี้ถูกสร้างจากระบบอนุญาตทำงานคนต่างด้าวที่มีสถานะการทำงานไม่ถูกต้องตามกฎหมาย ตามมติคณะรัฐมนตรีเมื่อวันที่ 24 กันยายน 2567<br>โดยกรมการจัดหางาน กระทรวงแรงงาน<br>พิมพ์เอกสาร วันที่ 10/04/2568 ${timestamp} `; |
| document.getElementById('timestamp').innerHTML = workPermitTimestamp; |
| document.getElementById('receiptTimestamp').innerHTML = workPermitTimestamp; |
| return; |
| } |
| |
| const worker = allWorkerData[index]; |
| document.getElementById('requestNumber').innerHTML = `<b>${worker.requestNumber || 'N/A'}</b>`; |
| document.getElementById('englishName').innerHTML = `<b>${worker.englishName || 'N/A'}</b>`; |
| document.getElementById('englishNameDuplicate').innerHTML = worker.englishName || 'N/A'; |
| document.getElementById('thaiName').innerHTML = `: ${worker.thaiName || 'N/A'}`; |
| document.getElementById('personalID').innerHTML = `: ${worker.personalID || 'N/A'}`; |
| document.getElementById('workPermitNumber').innerHTML = `: ${worker.workPermitNumber || 'N/A'}`; |
| document.getElementById('alienReferenceNumber').innerHTML = `: ${worker.alienReferenceNumber || 'N/A'}`; |
| document.getElementById('nationality').innerHTML = `: ${worker.nationality || 'N/A'}`; |
| document.getElementById('age').innerHTML = `: ${worker.age || 'N/A'}`; |
| document.getElementById('birthDate').innerHTML = `: ${worker.birthDate || 'N/A'}`; |
| document.getElementById('requestNumberReceipt').innerHTML = worker.requestNumber || 'N/A'; |
| document.getElementById('receiptNumberReceipt').innerHTML = worker.receiptNumber || 'N/A'; |
| document.getElementById('paymentNumberReceipt').innerHTML = worker.paymentNumber || 'N/A'; |
| document.getElementById('payerNameReceipt').innerHTML = worker.englishName || worker.thaiName || 'N/A'; |
| document.getElementById('nationalityReceipt').innerHTML = worker.nationality || 'N/A'; |
| document.getElementById('alienReferenceReceipt').innerHTML = worker.alienReferenceNumber || 'N/A'; |
| document.getElementById('personalIDReceipt').innerHTML = worker.personalID || 'N/A'; |
| setImageSrc('profilePic', worker.profileImage, defaultProfileSrc, worker.englishName, 'profile picture'); |
| |
| const currentDomain = window.location.origin; |
| const workerPageUrl = `${currentDomain}/worker.html?id=${encodeURIComponent(worker.requestNumber || '')}`; |
| const receiptUrl = `${currentDomain}/pdf.html?id=${encodeURIComponent(worker.requestNumber || '')}`; |
| |
| setImageSrc('qrCode', `https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=${encodeURIComponent(workerPageUrl)}`, defaultQrCodeSrc, worker.englishName, 'work permit QR code'); |
| setImageSrc('receiptQrCode', `https://api.qrserver.com/v1/create-qr-code/?size=300x300&ecc=H&data=${encodeURIComponent(receiptUrl)}`, defaultReceiptQrCodeSrc, worker.englishName, 'receipt QR code'); |
| |
| const timestamp = getCurrentTimestamp(); |
| const workPermitTimestamp = `เอกสารอิเล็กทรอนิกส์ฉบับนี้ถูกสร้างจากระบบอนุญาตทำงานคนต่างด้าวที่มีสถานะการทำงานไม่ถูกต้องตามกฎหมาย ตามมติคณะรัฐมนตรีเมื่อวันที่ 24 กันยายน 2567<br>โดยกรมการจัดหางาน กระทรวงแรงงาน<br>พิมพ์เอกสาร วันที่ 10/04/2568 ${timestamp} `; |
| document.getElementById('timestamp').innerHTML = workPermitTimestamp; |
| document.getElementById('receiptTimestamp').innerHTML = workPermitTimestamp; |
| } |
| |
| function clearWorkerData() { |
| document.getElementById('requestNumber').innerHTML = '<b>N/A</b>'; |
| document.getElementById('englishName').innerHTML = '<b>N/A</b>'; |
| document.getElementById('englishNameDuplicate').innerHTML = 'N/A'; |
| document.getElementById('thaiName').innerHTML = ': N/A'; |
| document.getElementById('personalID').innerHTML = ': N/A'; |
| document.getElementById('workPermitNumber').innerHTML = ': N/A'; |
| document.getElementById('alienReferenceNumber').innerHTML = ': N/A'; |
| document.getElementById('nationality').innerHTML = ': N/A'; |
| document.getElementById('age').innerHTML = ': N/A'; |
| document.getElementById('birthDate').innerHTML = ': N/A'; |
| document.getElementById('requestNumberReceipt').innerHTML = 'N/A'; |
| document.getElementById('receiptNumberReceipt').innerHTML = 'N/A'; |
| document.getElementById('paymentNumberReceipt').innerHTML = 'N/A'; |
| document.getElementById('payerNameReceipt').innerHTML = 'N/A'; |
| document.getElementById('nationalityReceipt').innerHTML = 'N/A'; |
| document.getElementById('alienReferenceReceipt').innerHTML = 'N/A'; |
| document.getElementById('personalIDReceipt').innerHTML = 'N/A'; |
| } |
| |
| |
| function showProgress(show, current = 0, total = 0) { |
| if (show) { |
| progressContainer.classList.add('visible'); |
| const percent = total > 0 ? Math.round((current / total) * 100) : 0; |
| progressFill.style.width = `${percent}%`; |
| progressText.textContent = `กำลังดาวน์โหลด ${current}/${total} (${percent}%)`; |
| |
| |
| if (percent >= 90) { |
| progressFill.style.backgroundColor = '#10b981'; |
| } else if (percent >= 50) { |
| progressFill.style.backgroundColor = '#f59e0b'; |
| } else { |
| progressFill.style.backgroundColor = '#3b82f6'; |
| } |
| } else { |
| progressContainer.classList.remove('visible'); |
| progressFill.style.width = '0%'; |
| } |
| } |
| |
| |
| function setButtonsDisabled(disabled) { |
| downloadCurrentBtn.disabled = disabled; |
| downloadSelectedBtn.disabled = disabled || selectedWorkers.size === 0; |
| downloadAllBtn.disabled = disabled; |
| randomizeBtn.disabled = disabled; |
| grayscaleBtn.disabled = disabled; |
| } |
| |
| |
| async function downloadPDF(workerItem, isBatchDownload = false) { |
| try { |
| |
| const originalIndex = currentIndex; |
| const originalGrayscale = isGrayscaleEnabled; |
| |
| |
| if (workerItem.requestNumber !== allWorkerData[currentIndex]?.requestNumber) { |
| const tempIndex = allWorkerData.findIndex(w => w.requestNumber === workerItem.requestNumber); |
| if (tempIndex !== -1) { |
| displayWorkerData(tempIndex); |
| await new Promise(resolve => setTimeout(resolve, 500)); |
| } |
| } |
| |
| |
| randomizeBothOverlays(); |
| await new Promise(resolve => setTimeout(resolve, 200)); |
| |
| |
| const controls = document.querySelector('.controls-container'); |
| if (controls) controls.style.display = 'none'; |
| |
| |
| const images = document.querySelectorAll('img'); |
| await Promise.all(Array.from(images).map(img => { |
| return new Promise(resolve => { |
| if (img.complete && img.naturalWidth > 0) { |
| resolve(); |
| } else { |
| const timer = setTimeout(() => { |
| console.warn(`Timeout loading image: ${img.src}`); |
| resolve(); |
| }, 5000); |
| |
| img.onload = () => { |
| clearTimeout(timer); |
| resolve(); |
| }; |
| img.onerror = () => { |
| clearTimeout(timer); |
| console.error(`Failed to load image: ${img.src}`); |
| resolve(); |
| }; |
| } |
| }); |
| })); |
| |
| |
| const cleanName = (workerItem.englishName || workerItem.thaiName || 'unknown') |
| .replace(/[^a-zA-Z0-9ก-๙]/g, '_') |
| .substring(0, 20); |
| const reqNum = workerItem.requestNumber || '0000'; |
| const last4 = reqNum.slice(-4); |
| const filename = `${last4}_${cleanName}_work_permit.pdf`; |
| |
| |
| const container = document.createElement('div'); |
| container.className = 'pdf-export-container'; |
| container.style.width = '892px'; |
| container.style.height = '2522px'; |
| container.style.position = 'absolute'; |
| container.style.left = '-9999px'; |
| container.style.top = '0'; |
| |
| |
| const page1Clone = document.getElementById('page1-div').cloneNode(true); |
| const page2Clone = document.getElementById('page2-div').cloneNode(true); |
| |
| |
| [page1Clone, page2Clone].forEach(page => { |
| page.style.margin = '0'; |
| page.style.padding = '0'; |
| page.style.border = 'none'; |
| |
| |
| page.querySelectorAll('img').forEach(img => { |
| img.style.filter = 'grayscale(100%)'; |
| img.style.webkitFilter = 'grayscale(100%)'; |
| }); |
| }); |
| |
| container.appendChild(page1Clone); |
| container.appendChild(page2Clone); |
| document.body.appendChild(container); |
| |
| |
| await new Promise(resolve => setTimeout(resolve, 300)); |
| |
| |
| const opt = { |
| margin: [0, 0, 0, 0], |
| filename: filename, |
| image: { |
| type: 'jpeg', |
| quality: 0.95 |
| }, |
| html2canvas: { |
| scale: 2, |
| useCORS: true, |
| allowTaint: true, |
| backgroundColor: '#ffffff', |
| logging: false, |
| width: 892, |
| height: 2522, |
| scrollX: 0, |
| scrollY: 0, |
| windowWidth: 892 |
| }, |
| jsPDF: { |
| unit: 'px', |
| format: [892, 1261], |
| orientation: 'portrait', |
| compress: true |
| } |
| }; |
| |
| await html2pdf().set(opt).from(container).save(); |
| |
| |
| document.body.removeChild(container); |
| if (controls) controls.style.display = 'block'; |
| |
| |
| if (originalIndex !== currentIndex) { |
| displayWorkerData(originalIndex); |
| } |
| |
| |
| if (originalGrayscale !== isGrayscaleEnabled) { |
| toggleGrayscale(); |
| } |
| |
| return true; |
| |
| } catch (error) { |
| console.error('Error generating PDF:', error); |
| |
| |
| const controls = document.querySelector('.controls-container'); |
| if (controls) controls.style.display = 'block'; |
| |
| |
| if (currentIndex >= 0 && currentIndex < allWorkerData.length) { |
| displayWorkerData(currentIndex); |
| } |
| |
| if (!isBatchDownload) { |
| alert('เกิดข้อผิดพลาดในการสร้าง PDF กรุณาลองใหม่อีกครั้ง'); |
| } |
| |
| return false; |
| } |
| } |
| |
| async function downloadCurrentPDF() { |
| if (currentIndex >= 0 && currentIndex < allWorkerData.length) { |
| setButtonsDisabled(true); |
| try { |
| await downloadPDF(allWorkerData[currentIndex], false); |
| } catch (error) { |
| alert('เกิดข้อผิดพลาดในการสร้าง PDF กรุณาลองใหม่อีกครั้ง'); |
| } |
| setButtonsDisabled(false); |
| } else { |
| alert('ไม่มีข้อมูลพนักงานที่เลือก'); |
| } |
| } |
| |
| async function downloadSelectedPDFs() { |
| if (selectedWorkers.size === 0) { |
| alert('กรุณาเลือกพนักงานอย่างน้อย 1 รายการ'); |
| return; |
| } |
| |
| const selectedArray = Array.from(selectedWorkers).sort((a, b) => a - b); |
| const confirmDownload = confirm(`คุณต้องการดาวน์โหลดใบอนุญาตทำงานสำหรับพนักงาน ${selectedArray.length} รายการใช่หรือไม่?\n\nกระบวนการนี้อาจใช้เวลาสักครู่`); |
| if (!confirmDownload) return; |
| |
| setButtonsDisabled(true); |
| showProgress(true, 0, selectedArray.length); |
| |
| let successCount = 0; |
| let failCount = 0; |
| const failList = []; |
| |
| for (let i = 0; i < selectedArray.length; i++) { |
| const workerIndex = selectedArray[i]; |
| const worker = allWorkerData[workerIndex]; |
| |
| showProgress(true, i + 1, selectedArray.length); |
| |
| try { |
| const success = await downloadPDF(worker, true); |
| if (success) { |
| successCount++; |
| } else { |
| failCount++; |
| failList.push(worker.requestNumber || `index: ${workerIndex}`); |
| } |
| |
| |
| if (i < selectedArray.length - 1) { |
| await new Promise(resolve => setTimeout(resolve, 1000)); |
| } |
| |
| } catch (error) { |
| console.error(`Error downloading PDF for worker at index ${workerIndex}:`, error); |
| failCount++; |
| failList.push(worker.requestNumber || `index: ${workerIndex}`); |
| } |
| } |
| |
| showProgress(false); |
| setButtonsDisabled(false); |
| |
| |
| let resultMessage = `ดาวน์โหลดสำเร็จ ${successCount} รายการ`; |
| if (failCount > 0) { |
| resultMessage += `\nล้มเหลว ${failCount} รายการ`; |
| if (failList.length > 0) { |
| resultMessage += `\nรายการที่ล้มเหลว: ${failList.slice(0, 5).join(', ')}${failList.length > 5 ? '...' : ''}`; |
| } |
| } |
| |
| alert(resultMessage); |
| |
| |
| if (currentIndex >= 0 && currentIndex < allWorkerData.length) { |
| displayWorkerData(currentIndex); |
| } |
| } |
| |
| async function downloadAllPDFs() { |
| if (!allWorkerData || allWorkerData.length === 0) { |
| alert('ไม่มีข้อมูลพนักงาน'); |
| return; |
| } |
| |
| const confirmDownload = confirm(`คุณต้องการดาวน์โหลดใบอนุญาตทำงานสำหรับพนักงานทั้งหมด ${allWorkerData.length} รายการใช่หรือไม่?\n\nกระบวนการนี้อาจใช้เวลานาน กรุณาอย่าปิดเบราว์เซอร์`); |
| if (!confirmDownload) return; |
| |
| setButtonsDisabled(true); |
| showProgress(true, 0, allWorkerData.length); |
| |
| let successCount = 0; |
| let failCount = 0; |
| const failList = []; |
| |
| for (let i = 0; i < allWorkerData.length; i++) { |
| showProgress(true, i + 1, allWorkerData.length); |
| |
| try { |
| const success = await downloadPDF(allWorkerData[i], true); |
| if (success) { |
| successCount++; |
| } else { |
| failCount++; |
| failList.push(allWorkerData[i].requestNumber || `index: ${i}`); |
| } |
| |
| |
| if (i < allWorkerData.length - 1) { |
| await new Promise(resolve => setTimeout(resolve, 800)); |
| } |
| |
| } catch (error) { |
| console.error(`Error downloading PDF for worker at index ${i}:`, error); |
| failCount++; |
| failList.push(allWorkerData[i].requestNumber || `index: ${i}`); |
| } |
| } |
| |
| showProgress(false); |
| setButtonsDisabled(false); |
| |
| |
| let resultMessage = `ดาวน์โหลดสำเร็จ ${successCount} รายการจากทั้งหมด ${allWorkerData.length} รายการ`; |
| if (failCount > 0) { |
| resultMessage += `\nล้มเหลว ${failCount} รายการ`; |
| if (failList.length > 0) { |
| resultMessage += `\nรายการที่ล้มเหลว (5 รายการแรก): ${failList.slice(0, 5).join(', ')}`; |
| } |
| } |
| |
| alert(resultMessage); |
| |
| |
| if (currentIndex >= 0 && currentIndex < allWorkerData.length) { |
| displayWorkerData(currentIndex); |
| } else if (allWorkerData.length > 0) { |
| displayWorkerData(0); |
| } |
| } |
| |
| window.onload = async () => { |
| try { |
| await loadWorkerData(); |
| randomizeBothOverlays(); |
| |
| |
| const style = document.createElement('style'); |
| style.textContent = ` |
| .highlight { |
| background-color: #ffeb3b; |
| color: #000; |
| font-weight: bold; |
| padding: 0 2px; |
| border-radius: 2px; |
| } |
| .dropdown-item-meta { |
| font-size: 12px; |
| color: #666; |
| margin-top: 2px; |
| } |
| .dropdown-item-meta span { |
| margin-right: 4px; |
| } |
| #selectedCount.has-selection { |
| color: #3b82f6; |
| font-weight: bold; |
| } |
| `; |
| document.head.appendChild(style); |
| |
| } catch (e) { |
| console.error('Error initializing application:', e); |
| } |
| }; |
| </script> |
| </body> |
| </html> |