ใบสมัครแพทย์ประจำบ้าน ประจำปีการศึกษา 2570

ภาควิชาโสต ศอ นาสิกวิทยา คณะแพทยศาสตร์ จุฬาลงกรณ์มหาวิทยาลัย

1. ข้อมูลส่วนตัว

2. ที่อยู่ปัจจุบัน

3. ที่อยู่ตามทะเบียนบ้าน

4. ข้อมูลต้นสังกัด

5. ประวัติการศึกษา และคุณวุฒิทางการศึกษา

ผลการสอบ ศรว. (NL)

6. คุณสมบัติ / ประวัติการปฏิบัติงาน

สถานที่ปฏิบัติงาน 3 ที่สุดท้ายก่อนเข้ารับการฝึกอบรม

ลำดับที่ 1 (ล่าสุด)
ลำดับที่ 2
ลำดับที่ 3

7. สถาบันฝึกอบรมที่ต้องการสมัคร

สามารถสมัครได้มากกว่า 1 สถาบัน

8. อัปโหลดเอกสารประกอบการสมัคร

รองรับไฟล์นามสกุล .pdf, .jpg, .png (แนะนำไม่เกิน 15MB ต่อไฟล์ — หากไฟล์ใหญ่เกิน ระบบจะแจ้งเตือนให้บีบอัดก่อนส่ง) (หากต้องการเลือกหลายไฟล์ในช่องเดียวกัน ให้กดปุ่ม Ctrl หรือ Cmd ค้างไว้ขณะเลือกไฟล์)

หมายเหตุ: กรุณาตรวจสอบความถูกต้องก่อนกดส่งข้อมูล และรอระบบอัปโหลดไฟล์สักครู่จนกว่าจะแสดงหน้าต่างยืนยัน

มีปัญหาในการอัปโหลด โทร 02-256-4000 ต่อ 61724 (เกศแก้ว พี่เดียร์) หรือแอดไลน์ https://lin.ee/Qiwc4wm

} } function searchData() { const searchId = document.getElementById('searchId').value.trim(); const statusText = document.getElementById('searchStatus'); const uploadForm = document.getElementById('uploadForm'); if (!searchId) { statusText.innerText = 'กรุณากรอกรหัสผู้สมัคร หรือ เลขบัตรประชาชน'; statusText.className = 'mt-3 font-medium text-red-500'; uploadForm.classList.add('hidden'); return; } statusText.innerText = 'กำลังค้นหาข้อมูล...'; statusText.className = 'mt-3 font-medium text-blue-500'; fetch(`${SCRIPT_URL}?action=search&searchId=${encodeURIComponent(searchId)}`) .then(r => r.json()) .then(result => { if (result.status === 'success') { const d = result.data; currentApplicant = { appId: d.appId, first: d.firstNameTh || '', last: d.lastNameTh || '' }; statusText.innerText = 'พบข้อมูลผู้สมัคร'; statusText.className = 'mt-3 font-bold text-green-600'; document.getElementById('uploadApplicant').innerText = `ผู้สมัคร: ${d.titleTh || ''}${currentApplicant.first} ${currentApplicant.last} (รหัส ${currentApplicant.appId})`; buildUploadItems(); uploadForm.classList.remove('hidden'); } else { statusText.innerText = 'ไม่พบข้อมูลผู้สมัครนี้ กรุณาตรวจสอบรหัสอีกครั้ง'; statusText.className = 'mt-3 font-medium text-red-500'; uploadForm.classList.add('hidden'); } }) .catch(() => { statusText.innerText = 'เกิดข้อผิดพลาดในการเชื่อมต่อระบบ'; statusText.className = 'mt-3 font-medium text-red-500'; }); } function buildUploadItems() { const box = document.getElementById('uploadItems'); box.innerHTML = UPLOAD_DOCS.map(d => `
`).join(''); } function setUpProgress(percent, text) { document.getElementById('upProg').classList.remove('hidden'); document.getElementById('upProgBar').style.width = percent + '%'; document.getElementById('upProgText').innerText = text; } async function submitUpload(event) { event.preventDefault(); if (!currentApplicant) return; const jobs = []; for (const d of UPLOAD_DOCS) { const files = Array.from(document.getElementById('up_' + d.field).files); if (files.length === 0) continue; const modeEl = document.querySelector(`input[name="mode_${d.field}"]:checked`); if (!modeEl) { alert(`กรุณาเลือก "เพิ่มเติม" หรือ "แก้ไข" ให้หัวข้อ:\n${d.title}`); return; } for (const f of files) { if (f.size > MAX_FILE_MB * 1024 * 1024) { alert(`ไฟล์ "${f.name}" ใหญ่เกิน ${MAX_FILE_MB}MB กรุณาบีบอัดก่อนอัปโหลด`); return; } } files.forEach((f, i) => jobs.push({ doc: d, file: f, mode: modeEl.value, idx: i, total: files.length })); } if (jobs.length === 0) { alert('กรุณาเลือกไฟล์อย่างน้อย 1 รายการ'); return; } const replaceTitles = [...new Set(jobs.filter(j => j.mode === 'replace').map(j => j.doc.label))]; if (replaceTitles.length && !confirm(`ท่านเลือก "แก้ไข" ในหัวข้อ: ${replaceTitles.join(', ')}\n\nทางภาควิชาจะยึดเอกสารที่อัปโหลดล่าสุดเป็นอันเสร็จสิ้น ยืนยันหรือไม่?`)) return; const btn = document.getElementById('upSubmitBtn'); const original = btn.innerText; btn.disabled = true; try { for (let i = 0; i < jobs.length; i++) { const { doc, file, mode, idx, total } = jobs[i]; btn.innerText = `กำลังอัปโหลด ${i + 1}/${jobs.length}...`; setUpProgress(5 + Math.round((i / jobs.length) * 85), `กำลังอัปโหลด ${i + 1}/${jobs.length}: ${file.name}`); const toSend = await compressImageIfNeeded(file); const base64 = await readFileAsBase64(toSend); let ext = (toSend.name.split('.').pop() || '').toLowerCase(); if (toSend.type === 'image/jpeg' && ext === 'png') ext = 'jpg'; const suffix = mode === 'replace' ? 'แก้ไข' : 'เพิ่มเติม'; const num = total > 1 ? `_${idx + 1}` : ''; const newName = `${currentApplicant.first}_${currentApplicant.last}_${doc.label}_${suffix}${num}.${ext}` .replace(/[\\/:*?"<>|]/g, '-'); const res = await fetch(SCRIPT_URL, { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify({ action: 'uploadFile', appId: currentApplicant.appId, field: doc.field, mode: (mode === 'replace' && idx === 0) ? 'replace' : 'append', name: newName, mimeType: toSend.type, base64 }) }); const result = await res.json(); if (result.status !== 'success') throw new Error(`อัปโหลด "${file.name}" ไม่สำเร็จ: ${result.message || ''}`); } btn.innerText = 'กำลังสร้างเอกสาร PDF และส่งอีเมลยืนยัน...'; setUpProgress(97, 'กำลังสร้างเอกสาร PDF และส่งอีเมลยืนยัน...'); // แก้ไขให้ส่งแบบ POST ไปยัง doPost เหมือนฟังก์ชันหลัก const fin = await fetch(SCRIPT_URL, { method: 'POST', headers: { 'Content-Type': 'text/plain;charset=utf-8' }, body: JSON.stringify({ action: 'finalize', appId: currentApplicant.appId, isUpdate: true }) }); const finResult = await fin.json(); if (finResult.status !== 'success') throw new Error(finResult.message || 'สร้างเอกสาร PDF ไม่สำเร็จ'); setUpProgress(100, 'เสร็จสิ้น!'); alert('อัปโหลดเอกสารเรียบร้อยแล้ว ระบบส่งอีเมลยืนยันให้ท่านแล้ว'); buildUploadItems(); } catch (err) { console.error(err); alert('เกิดข้อผิดพลาด: ' + err.message); } finally { btn.innerText = original; btn.disabled = false; document.getElementById('upProg').classList.add('hidden'); } } function readFileAsBase64(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result.split(',')[1]); reader.onerror = error => reject(error); reader.readAsDataURL(file); }); } function compressImageIfNeeded(file) { return new Promise((resolve) => { if (!file.type || !file.type.startsWith('image/')) { resolve(file); return; } const reader = new FileReader(); reader.onload = (e) => { const img = new Image(); img.onload = () => { let { width, height } = img; if (width > IMAGE_MAX_DIMENSION || height > IMAGE_MAX_DIMENSION) { const scale = IMAGE_MAX_DIMENSION / Math.max(width, height); width = Math.round(width * scale); height = Math.round(height * scale); } const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; canvas.getContext('2d').drawImage(img, 0, 0, width, height); canvas.toBlob((blob) => { if (!blob || blob.size >= file.size) { resolve(file); } else { resolve(new File([blob], file.name, { type: 'image/jpeg' })); } }, 'image/jpeg', IMAGE_QUALITY); }; img.onerror = () => resolve(file); img.src = e.target.result; }; reader.onerror = () => resolve(file); reader.readAsDataURL(file); }); } function setProgress(percent, text) { const wrap = document.getElementById('uploadProgress'); const bar = document.getElementById('uploadProgressBar'); const label = document.getElementById('uploadProgressText'); wrap.classList.remove('hidden'); bar.style.width = percent + '%'; label.innerText = text; } async function submitForm(event) { event.preventDefault(); const submitBtn = document.getElementById('submitBtn'); const originalText = submitBtn.innerText; const form = document.getElementById('applicationForm'); const formData = new FormData(form); const textData = {}; const fileEntries = []; for (const [key, value] of formData.entries()) { if (value instanceof File && value.name !== "") { if (value.size > MAX_FILE_MB * 1024 * 1024) { alert(`ไฟล์ "${value.name}" มีขนาด ${(value.size / 1024 / 1024).toFixed(1)}MB ซึ่งเกิน ${MAX_FILE_MB}MB ต่อไฟล์\n\nกรุณาบีบอัดไฟล์ PDF หรือถ่ายรูปใหม่ให้มีขนาดเล็กลง แล้วลองอัปโหลดอีกครั้ง`); return; } fileEntries.push({ field: key, file: value }); } else if (!(value instanceof File)) { textData[key] = value; } } let appId = document.getElementById('appId').value; const isNewApplication = !appId; textData.appId = appId; submitBtn.disabled = true; try { submitBtn.innerText = 'กำลังบันทึกข้อมูลฟอร์ม...'; setProgress(5, 'กำลังบันทึกข้อมูลฟอร์ม...'); const saveRes = await fetch(SCRIPT_URL, { method: 'POST', body: JSON.stringify({ action: 'saveData', ...textData }), headers: { 'Content-Type': 'text/plain;charset=utf-8' } }); const saveResult = await saveRes.json(); if (saveResult.status !== 'success') { throw new Error(saveResult.message || 'บันทึกข้อมูลฟอร์มไม่สำเร็จ'); } if (isNewApplication) { appId = saveResult.id; document.getElementById('appId').value = appId; } for (let i = 0; i < fileEntries.length; i++) { const { field, file } = fileEntries[i]; const percent = 10 + Math.round((i / Math.max(fileEntries.length, 1)) * 85); submitBtn.innerText = `กำลังอัปโหลดไฟล์ ${i + 1}/${fileEntries.length}...`; setProgress(percent, `กำลังอัปโหลดไฟล์ ${i + 1}/${fileEntries.length}: ${file.name}`); const fileToSend = await compressImageIfNeeded(file); const base64 = await readFileAsBase64(fileToSend); const res = await fetch(SCRIPT_URL, { method: 'POST', body: JSON.stringify({ action: 'uploadFile', appId, field, name: fileToSend.name, mimeType: fileToSend.type, base64 }), headers: { 'Content-Type': 'text/plain;charset=utf-8' } }); const result = await res.json(); if (result.status !== 'success') { throw new Error(`อัปโหลดไฟล์ "${file.name}" ไม่สำเร็จ: ${result.message || ''}`); } } submitBtn.innerText = 'กำลังสร้างเอกสาร PDF และส่งอีเมลยืนยัน...'; setProgress(97, 'กำลังสร้างเอกสาร PDF และส่งอีเมลยืนยัน...'); // แก้ไขการเรียก finalize ให้ส่งเป็น POST JSON เพื่อไม่ให้เกิดปัญหา action ไม่รู้จัก const finalizeRes = await fetch(SCRIPT_URL, { method: 'POST', body: JSON.stringify({ action: 'finalize', appId: appId, isUpdate: !isNewApplication }), headers: { 'Content-Type': 'text/plain;charset=utf-8' } }); const finalizeResult = await finalizeRes.json(); if (finalizeResult.status !== 'success') { throw new Error(finalizeResult.message || 'สร้างเอกสาร PDF ไม่สำเร็จ'); } setProgress(100, 'เสร็จสิ้น!'); alert(isNewApplication ? 'ส่งใบสมัครสำเร็จ! รหัสผู้สมัครของคุณคือ: ' + appId + '\nกรุณาตรวจสอบอีเมลตอบกลับเพื่อยืนยัน' : 'อัปเดตข้อมูลและไฟล์เรียบร้อยแล้ว!'); if (isNewApplication) { form.reset(); document.getElementById('appId').value = ''; window.scrollTo(0,0); } else { document.getElementById('searchStatus').innerText = 'ข้อมูลของคุณถูกอัปเดตเรียบร้อยแล้ว'; } } catch (error) { console.error(error); alert('เกิดข้อผิดพลาดในการส่งข้อมูล: ' + error.message); } finally { submitBtn.innerText = originalText; submitBtn.disabled = false; document.getElementById('uploadProgress').classList.add('hidden'); } } function copyAddress() { const isChecked = document.getElementById('sameAddress').checked; const fields = ['no', 'village', 'moo', 'road', 'subdist', 'dist', 'prov', 'zip']; fields.forEach(field => { const target = document.getElementById('addr2_' + field); if(isChecked) { target.value = document.querySelector(`input[name="addr1_${field}"]`).value; target.readOnly = true; target.classList.add('bg-gray-100'); } else { target.value = ''; target.readOnly = false; target.classList.remove('bg-gray-100'); } }); } function toggleSponsor() { const select = document.getElementById('hasSponsor'); const input = document.getElementById('sponsorName'); const label = document.getElementById('sponsorLabel'); if(select.selectedIndex === 1 || select.value === 'มี') { input.disabled = false; input.removeAttribute('disabled'); input.classList.remove('bg-gray-50', 'border-gray-200'); input.classList.add('border-gray-300'); label.classList.remove('text-gray-400'); label.classList.add('text-gray-800'); input.focus(); } else { input.value = ''; input.disabled = true; input.setAttribute('disabled', 'true'); input.classList.add('bg-gray-50', 'border-gray-200'); input.classList.remove('border-gray-300'); label.classList.add('text-gray-400'); label.classList.remove('text-gray-800'); } }