Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,417 @@
<!DOCTYPE html>
<html lang="th" class="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kiosk - พิมพ์สลิปเงินเดือนและใบเสียภาษี</title>
<link rel="icon" type="image/png" href="<?= BASE_URL ?>/public/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="<?= BASE_URL ?>/public/favicon.ico">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Kanit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['Kanit', 'sans-serif'],
}
}
}
}
</script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<style>
.kiosk-btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
background: linear-gradient(to bottom, #ffffff, #f1f5f9);
border: 1px solid #cbd5e1;
border-bottom-width: 6px;
border-radius: 1rem;
font-size: 2.5rem;
font-weight: 700;
color: #334155;
box-shadow: 0 4px 6px rgba(0,0,0,0.1), inset 0 -2px 0 rgba(0,0,0,0.05);
transition: all 0.1s ease;
cursor: pointer;
user-select: none;
height: 5.5rem;
}
.kiosk-btn:active {
transform: translateY(4px);
border-bottom-width: 2px;
margin-top: 4px;
box-shadow: 0 1px 2px rgba(0,0,0,0.1), inset 0 2px 4px rgba(0,0,0,0.1);
background: #f8fafc;
}
.kiosk-btn.btn-danger {
color: #ef4444;
background: linear-gradient(to bottom, #fef2f2, #fee2e2);
border-color: #fca5a5;
border-bottom-color: #f87171;
}
.kiosk-btn.btn-danger:active {
background: #fee2e2;
}
.kiosk-btn.btn-gray {
color: #64748b;
background: linear-gradient(to bottom, #f8fafc, #f1f5f9);
border-color: #cbd5e1;
border-bottom-color: #94a3b8;
}
.kiosk-btn.btn-gray:active {
background: #f1f5f9;
}
.digit-box {
@apply w-8 h-12 md:w-11 md:h-14 flex items-center justify-center text-3xl font-bold bg-white border-2 border-slate-200 rounded-lg shadow-inner text-slate-800 transition-colors;
}
.digit-box.filled {
@apply border-sky-400 bg-sky-50 text-sky-700 shadow-sm;
}
.digit-box.active {
@apply border-sky-500 ring-2 ring-sky-200 shadow-sm scale-110 transform transition-transform;
}
.numpad-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
/* Hide scrollbar for clean kiosk look */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
</style>
</head>
<body class="bg-gradient-to-br from-slate-100 to-slate-200 min-h-screen flex flex-col items-center justify-center p-4 sm:p-8">
<div class="w-full max-w-6xl flex flex-col md:flex-row gap-8 bg-white/80 backdrop-blur-xl p-8 rounded-[2.5rem] shadow-2xl border border-white/50">
<!-- Left Side: Numpad -->
<div class="flex-1 flex flex-col items-center border-b md:border-b-0 md:border-r border-slate-200 pb-8 md:pb-0 md:pr-8">
<h1 class="text-3xl font-bold text-slate-800 mb-2">ตรวจสอบข้อมูลเอกสาร</h1>
<p class="text-slate-500 mb-8 text-center text-lg">กรุณาระบุเลขประจำตัวประชาชน 13 หลัก</p>
<div class="w-full max-w-lg mb-8 relative flex justify-center gap-1.5 md:gap-2 items-center" id="digit_container">
<div class="digit-box active" id="digit-0"></div>
<div class="text-xl md:text-2xl font-bold text-slate-300">-</div>
<div class="digit-box" id="digit-1"></div>
<div class="digit-box" id="digit-2"></div>
<div class="digit-box" id="digit-3"></div>
<div class="digit-box" id="digit-4"></div>
<div class="text-xl md:text-2xl font-bold text-slate-300">-</div>
<div class="digit-box" id="digit-5"></div>
<div class="digit-box" id="digit-6"></div>
<div class="digit-box" id="digit-7"></div>
<div class="digit-box" id="digit-8"></div>
<div class="digit-box" id="digit-9"></div>
<div class="text-xl md:text-2xl font-bold text-slate-300">-</div>
<div class="digit-box" id="digit-10"></div>
<div class="digit-box" id="digit-11"></div>
<div class="text-xl md:text-2xl font-bold text-slate-300">-</div>
<div class="digit-box" id="digit-12"></div>
</div>
<div class="w-full max-w-sm numpad-container">
<button type="button" class="kiosk-btn" onclick="pressKey('1')">1</button>
<button type="button" class="kiosk-btn" onclick="pressKey('2')">2</button>
<button type="button" class="kiosk-btn" onclick="pressKey('3')">3</button>
<button type="button" class="kiosk-btn" onclick="pressKey('4')">4</button>
<button type="button" class="kiosk-btn" onclick="pressKey('5')">5</button>
<button type="button" class="kiosk-btn" onclick="pressKey('6')">6</button>
<button type="button" class="kiosk-btn" onclick="pressKey('7')">7</button>
<button type="button" class="kiosk-btn" onclick="pressKey('8')">8</button>
<button type="button" class="kiosk-btn" onclick="pressKey('9')">9</button>
<button type="button" class="kiosk-btn btn-danger" onclick="clearKey()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-8 h-8"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" /></svg>
</button>
<button type="button" class="kiosk-btn" onclick="pressKey('0')">0</button>
<button type="button" class="kiosk-btn btn-gray" onclick="backspaceKey()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-8 h-8"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9.75L14.25 12m0 0l2.25 2.25M14.25 12l2.25-2.25M14.25 12L12 14.25m-2.58 4.92l-6.375-6.375a1.125 1.125 0 010-1.59L9.42 4.83c.211-.211.498-.33.796-.33H19.5a2.25 2.25 0 012.25 2.25v10.5a2.25 2.25 0 01-2.25 2.25h-9.284c-.298 0-.585-.119-.796-.33z" /></svg>
</button>
</div>
</div>
<!-- Right Side: Results -->
<div class="flex-1 flex flex-col" id="results_pane">
<div class="flex-1 flex flex-col items-center justify-center text-center opacity-50" id="waiting_state">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-32 h-32 text-slate-300 mb-6"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /></svg>
<p class="text-2xl font-medium text-slate-600">รอการระบุข้อมูล</p>
<p class="text-slate-500 mt-2">พิมพ์รหัสบัตรประชาชนเพื่อค้นหาเอกสาร</p>
</div>
<div class="flex-1 hidden flex-col" id="data_state">
<div class="bg-sky-50 rounded-2xl p-6 mb-6">
<p class="text-sky-600 font-medium text-sm">ผู้ใช้งาน</p>
<h2 class="text-2xl font-bold text-slate-800 mt-1" id="person_name">ชื่อ - สกุล</h2>
</div>
<!-- Tabs -->
<div class="flex gap-2 mb-6 p-1 bg-slate-100 rounded-xl">
<button class="flex-1 py-3 text-center rounded-lg font-semibold bg-white text-sky-600 shadow-sm transition-all" id="tab_slip" onclick="switchTab('slip')">สลิปเงินเดือน</button>
<button class="flex-1 py-3 text-center rounded-lg font-semibold text-slate-500 hover:text-slate-700 transition-all" id="tab_tax" onclick="switchTab('tax')">ใบเสียภาษี 50 ทวิ</button>
</div>
<div class="flex-1 overflow-y-auto pr-2 relative flex flex-col">
<!-- Slips List -->
<div id="slips_list_container" class="flex flex-col h-full">
<div id="slips_list" class="grid grid-cols-1 sm:grid-cols-2 gap-4 flex-1">
<!-- Rendered via JS -->
</div>
<div class="mt-4 pt-4 border-t border-slate-200 hidden" id="slip_bulk_action">
<button onclick="printSelectedSlips()" class="w-full py-4 bg-sky-500 hover:bg-sky-600 text-white rounded-xl font-semibold text-lg transition-colors flex items-center justify-center gap-2 shadow-md hover:shadow-lg">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0v2.756a2.25 2.25 0 002.25 2.25h6a2.25 2.25 0 002.25-2.25V9.229z" /></svg>
พิมพ์สลิปที่เลือก
</button>
</div>
</div>
<!-- Tax List -->
<div id="taxes_list" class="hidden grid grid-cols-1 sm:grid-cols-2 gap-4">
<!-- Rendered via JS -->
</div>
</div>
</div>
</div>
</div>
<!-- Alert Modal -->
<div id="alert_modal" class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm z-50 flex items-center justify-center hidden opacity-0 transition-opacity">
<div class="bg-white rounded-3xl p-8 max-w-sm w-full mx-4 text-center transform scale-95 transition-transform duration-300" id="alert_content">
<div class="w-20 h-20 bg-red-100 text-red-500 rounded-full flex items-center justify-center mx-auto mb-6">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-10 h-10"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
</div>
<h3 class="text-2xl font-bold text-slate-800 mb-2">ไม่พบข้อมูล</h3>
<p class="text-slate-500 mb-8" id="alert_message">กรุณาตรวจสอบรหัสบัตรประชาชนอีกครั้ง</p>
<button onclick="closeAlert()" class="w-full py-4 bg-slate-800 hover:bg-slate-700 text-white rounded-xl font-semibold text-lg transition-colors">ตกลง</button>
</div>
</div>
<script>
const nidInput = document.getElementById('nid_input');
let currentNid = '';
const thaiMonths = {
1: 'มกราคม', 2: 'กุมภาพันธ์', 3: 'มีนาคม', 4: 'เมษายน',
5: 'พฤษภาคม', 6: 'มิถุนายน', 7: 'กรกฎาคม', 8: 'สิงหาคม',
9: 'กันยายน', 10: 'ตุลาคม', 11: 'พฤศจิกายน', 12: 'ธันวาคม'
};
function pressKey(num) {
if (currentNid.length < 13) {
currentNid += num;
updateDisplay();
if (currentNid.length === 13) {
searchData();
}
}
}
function backspaceKey() {
if (currentNid.length > 0) {
currentNid = currentNid.slice(0, -1);
updateDisplay();
resetUI();
}
}
function clearKey() {
currentNid = '';
updateDisplay();
resetUI();
}
function updateDisplay() {
for (let i = 0; i < 13; i++) {
let el = document.getElementById('digit-' + i);
if (i < currentNid.length) {
el.innerText = currentNid[i];
el.classList.add('filled');
el.classList.remove('active');
} else if (i === currentNid.length) {
el.innerText = '';
el.classList.remove('filled');
el.classList.add('active');
} else {
el.innerText = '';
el.classList.remove('filled');
el.classList.remove('active');
}
}
}
function resetUI() {
$('#waiting_state').removeClass('hidden');
$('#data_state').addClass('hidden');
}
function showAlert(msg) {
$('#alert_message').text(msg);
const modal = $('#alert_modal');
modal.removeClass('hidden');
setTimeout(() => {
modal.removeClass('opacity-0');
$('#alert_content').removeClass('scale-95');
}, 10);
}
function closeAlert() {
const modal = $('#alert_modal');
modal.addClass('opacity-0');
$('#alert_content').addClass('scale-95');
setTimeout(() => {
modal.addClass('hidden');
clearKey();
}, 300);
}
function switchTab(tab) {
if (tab === 'slip') {
$('#tab_slip').addClass('bg-white text-sky-600 shadow-sm').removeClass('text-slate-500 hover:text-slate-700');
$('#tab_tax').removeClass('bg-white text-sky-600 shadow-sm').addClass('text-slate-500 hover:text-slate-700');
$('#slips_list_container').removeClass('hidden');
$('#taxes_list').addClass('hidden');
} else {
$('#tab_tax').addClass('bg-white text-sky-600 shadow-sm').removeClass('text-slate-500 hover:text-slate-700');
$('#tab_slip').removeClass('bg-white text-sky-600 shadow-sm').addClass('text-slate-500 hover:text-slate-700');
$('#taxes_list').removeClass('hidden');
$('#slips_list_container').addClass('hidden');
}
}
function searchData() {
$('#waiting_state').html(`
<div class="animate-spin rounded-full h-16 w-16 border-t-4 border-b-4 border-sky-500 mb-6"></div>
<p class="text-xl font-medium text-slate-600">กำลังค้นหาข้อมูล...</p>
`);
$.post('<?= BASE_URL ?>/kiosk/search', { national_id: currentNid }, function(response) {
if (response.status === 'success') {
$('#person_name').text(response.data.name);
// Render Slips
let slipsHtml = '';
if (response.data.slips.length > 0) {
response.data.slips.forEach(slip => {
let monthName = thaiMonths[parseInt(slip.month_no)];
let yearTh = parseInt(slip.year_no) > 2500 ? parseInt(slip.year_no) : parseInt(slip.year_no) + 543;
let paddedMonth = slip.month_no.toString().padStart(2, '0');
slipsHtml += `
<label class="flex items-center justify-between p-4 bg-white border border-slate-200 rounded-2xl cursor-pointer hover:border-sky-300 hover:shadow-md transition-all group has-[:checked]:border-sky-500 has-[:checked]:bg-sky-50/50 has-[:checked]:ring-1 has-[:checked]:ring-sky-500">
<div class="flex items-center gap-4">
<div class="relative flex items-center">
<input type="checkbox" class="slip-checkbox peer h-6 w-6 cursor-pointer appearance-none rounded-md border-2 border-slate-300 checked:border-sky-500 checked:bg-sky-500 transition-all" value="${paddedMonth}-${slip.year_no}">
<svg class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-4 h-4 text-white opacity-0 peer-checked:opacity-100 pointer-events-none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>
</div>
<div>
<p class="font-semibold text-slate-800 text-lg">เดือน ${monthName}</p>
<p class="text-sm text-slate-500">พ.ศ. ${yearTh}</p>
</div>
</div>
<a href="<?= BASE_URL ?>/kiosk/print/slip/${currentNid}/${paddedMonth}/${slip.year_no}" target="_blank" onclick="event.stopPropagation();" class="w-10 h-10 rounded-full bg-slate-50 flex items-center justify-center text-slate-400 hover:bg-sky-100 hover:text-sky-600 transition-colors" title="พิมพ์เดี่ยว">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0v2.756a2.25 2.25 0 002.25 2.25h6a2.25 2.25 0 002.25-2.25V9.229z" /></svg>
</a>
</label>
`;
});
$('#slip_bulk_action').removeClass('hidden');
} else {
slipsHtml = '<p class="text-slate-500 col-span-2 text-center py-4">ไม่พบข้อมูลสลิปเงินเดือน</p>';
$('#slip_bulk_action').addClass('hidden');
}
$('#slips_list').html(slipsHtml);
// Render Taxes
let taxesHtml = '';
if (response.data.taxes.length > 0) {
response.data.taxes.forEach(tax => {
let yearTh = parseInt(tax.year_no) > 2500 ? parseInt(tax.year_no) : parseInt(tax.year_no) + 543;
taxesHtml += `
<a href="<?= BASE_URL ?>/kiosk/print/tax/${currentNid}/${tax.year_no}" target="_blank" class="flex items-center justify-between p-4 bg-white border border-slate-200 rounded-2xl hover:border-orange-300 hover:shadow-md transition-all group">
<div>
<p class="font-semibold text-slate-800 text-lg">ใบ 50 ทวิ</p>
<p class="text-sm text-slate-500">ประจำปี ${yearTh}</p>
</div>
<div class="w-10 h-10 rounded-full bg-slate-50 flex items-center justify-center text-slate-400 group-hover:bg-orange-50 group-hover:text-orange-500 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
</div>
</a>
`;
});
} else {
taxesHtml = '<p class="text-slate-500 col-span-2 text-center py-4">ไม่พบข้อมูลใบเสียภาษี</p>';
}
$('#taxes_list').html(taxesHtml);
switchTab('slip');
$('#waiting_state').addClass('hidden');
$('#data_state').removeClass('hidden');
} else {
showAlert(response.message || 'ไม่พบข้อมูล');
$('#waiting_state').html(`
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" class="w-32 h-32 text-slate-300 mb-6"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" /></svg>
<p class="text-2xl font-medium text-slate-600">รอการระบุข้อมูล</p>
<p class="text-slate-500 mt-2">พิมพ์รหัสบัตรประชาชนเพื่อค้นหาเอกสาร</p>
`);
}
}, 'json').fail(function() {
showAlert('เกิดข้อผิดพลาดในการเชื่อมต่อเซิร์ฟเวอร์');
});
}
function printSelectedSlips() {
let selected = [];
$('.slip-checkbox:checked').each(function() {
selected.push($(this).val());
});
if (selected.length === 0) {
showAlert('กรุณาเลือกอย่างน้อย 1 รายการ');
return;
}
// Create a dynamic form to submit
let form = document.createElement('form');
form.method = 'POST';
form.action = '<?= BASE_URL ?>/kiosk/print/slipBulk';
form.target = '_blank';
let inputNid = document.createElement('input');
inputNid.type = 'hidden';
inputNid.name = 'national_id';
inputNid.value = currentNid;
form.appendChild(inputNid);
selected.forEach(val => {
let inputVal = document.createElement('input');
inputVal.type = 'hidden';
inputVal.name = 'periods[]';
inputVal.value = val;
form.appendChild(inputVal);
});
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
}
// Initialize display on load
updateDisplay();
</script>
</body>
</html>