Files
rmutr/schedule-table-prototype.html
Nut.ไปเรื่อย 2fbb0b3d51 feat: add Thai date selector, HTML prototypes, and SQL script
- Replace xlsx with exceljs in construction calculator; switch date input to Thai Buddhist Era selectors
- Add disbursement plan preview HTML and schedule table prototype
- Add sync_budget_come_from SQL script
- Add budget type select menu design plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 10:13:05 +07:00

459 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="th">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>25. ตารางกำหนดการดำเนินโครงการ — Prototype</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: 'Sarabun', sans-serif; background: #f5f5f5; padding: 24px; color: #333; }
@import url('https://fonts.googleapis.com/css2?family=Sarabun:wght@400;500;600&display=swap');
.card {
background: #fff;
border-radius: 8px;
padding: 24px;
max-width: 1100px;
margin: 0 auto;
box-shadow: 0 1px 4px rgba(0,0,0,.12);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #BD413A;
}
/* ปุ่มหลัก */
.btn-primary {
background: #BD413A;
color: #fff;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
font-family: inherit;
}
.btn-primary:hover { background: #a33630; }
.btn-secondary {
background: #6d7da5;
color: #fff;
border: none;
border-radius: 4px;
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
font-family: inherit;
}
.btn-secondary:hover { background: #566082; }
.btn-text {
background: none;
border: none;
color: #6d7da5;
font-size: 13px;
cursor: pointer;
padding: 4px 6px;
font-family: inherit;
}
.btn-text:hover { text-decoration: underline; }
.btn-danger-icon {
background: none;
border: none;
color: #BD413A;
cursor: pointer;
font-size: 20px;
line-height: 1;
padding: 2px 4px;
}
.btn-danger-icon:hover { color: #a33630; }
/* บล็อกวันที่ */
.date-block {
margin-bottom: 24px;
border-top: 1px dashed #d7d7d7;
padding-top: 16px;
}
.date-block:first-child { border-top: none; padding-top: 0; }
.date-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.date-row label { font-weight: 500; font-size: 14px; color: #555; white-space: nowrap; }
/* input วันที่ */
.input-date {
border: 1px solid #ccc;
border-radius: 4px;
padding: 6px 10px;
font-size: 14px;
font-family: inherit;
width: 160px;
}
.input-date:focus { outline: none; border-color: #6d7da5; }
/* ตาราง */
.schedule-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
}
.schedule-table th {
background: #f3f4f6;
color: #555;
font-weight: 600;
text-align: left;
padding: 8px 10px;
border: 1px solid #e0e0e0;
white-space: nowrap;
}
.schedule-table td {
padding: 6px 8px;
border: 1px solid #e8e8e8;
vertical-align: top;
}
.schedule-table tr:hover td { background: #fafafa; }
/* input ในตาราง */
.tbl-input {
border: 1px solid #ddd;
border-radius: 4px;
padding: 5px 8px;
font-size: 13px;
font-family: inherit;
width: 100%;
background: #fff;
}
.tbl-input:focus { outline: none; border-color: #6d7da5; box-shadow: 0 0 0 2px rgba(109,125,165,.15); }
.tbl-input-time { width: 90px; }
/* badge ประเภท */
.type-badge {
display: inline-block;
padding: 2px 8px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
margin-top: 4px;
}
.type-บรรยาย { background: #e3f0fb; color: #1565c0; }
.type-ปฏิบัติ { background: #e8f5e9; color: #2e7d32; }
.type-แบ่งกลุ่ม { background: #fff3e0; color: #e65100; }
.type-นำเสนอ { background: #f3e5f5; color: #6a1b9a; }
.type-ศึกษาดูงาน{ background: #fce4ec; color: #ad1457; }
.type-อื่นๆ { background: #f5f5f5; color: #555; }
/* lecturer row */
.lec-row {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
}
.lec-row .tbl-input { width: 130px; }
.radio-group { display: flex; gap: 8px; white-space: nowrap; font-size: 12px; align-items: center; }
.radio-group label { display: flex; align-items: center; gap: 3px; cursor: pointer; }
.radio-group input[type=radio] { accent-color: #BD413A; cursor: pointer; }
/* แถว เพิ่มแถว */
.add-row-cell {
text-align: right;
padding: 6px 8px !important;
border: 1px solid #e8e8e8;
}
/* hint */
.hint {
font-size: 11px;
color: #999;
margin-top: 2px;
}
/* no data */
.empty-row td {
text-align: center;
color: #aaa;
font-size: 13px;
padding: 20px;
}
/* wrapper autocomplete */
.autocomplete-wrap { position: relative; }
.autocomplete-wrap datalist { display: none; }
.type-preview-wrap { min-height: 20px; }
/* footer */
.page-footer {
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>
</head>
<body>
<div class="card">
<div class="section-header">
<span class="section-title">25. ตารางกำหนดการดำเนินโครงการ</span>
<button class="btn-primary" onclick="addDateBlock()">+ เพิ่มวันที่</button>
</div>
<div id="date-blocks-container">
<!-- Date blocks inserted here -->
</div>
<div class="page-footer">
<button class="btn-secondary" style="background:#888;">ย้อนกลับ</button>
<button class="btn-primary" onclick="alert('บันทึกสำเร็จ!')">บันทึก</button>
</div>
</div>
<!-- Datalist สำหรับ autocomplete ประเภท -->
<datalist id="topic-types-list">
<option value="บรรยาย">
<option value="ปฏิบัติ">
<option value="อภิปราย">
<option value="แบ่งกลุ่มปฏิบัติ">
<option value="นำเสนอผลงาน">
<option value="ศึกษาดูงาน">
</datalist>
<!-- Datalist สำหรับ autocomplete เวลา -->
<datalist id="time-list">
<option value="08:00">
<option value="08:30">
<option value="09:00">
<option value="09:30">
<option value="10:00">
<option value="10:30">
<option value="11:00">
<option value="11:30">
<option value="12:00">
<option value="12:30">
<option value="13:00">
<option value="13:30">
<option value="14:00">
<option value="14:30">
<option value="15:00">
<option value="15:30">
<option value="16:00">
<option value="16:30">
<option value="17:00">
<option value="17:30">
<option value="18:00">
<option value="18:30">
<option value="19:00">
<option value="19:30">
<option value="20:00">
</datalist>
<script>
let dateBlockCount = 0;
function typeBadgeClass(v) {
if (!v) return '';
if (v.includes('บรรยาย')) return 'type-บรรยาย';
if (v.includes('ปฏิบัติ') && !v.includes('แบ่ง')) return 'type-ปฏิบัติ';
if (v.includes('แบ่งกลุ่ม')) return 'type-แบ่งกลุ่ม';
if (v.includes('นำเสนอ')) return 'type-นำเสนอ';
if (v.includes('ศึกษาดูงาน')) return 'type-ศึกษาดูงาน';
if (v.includes('อภิปราย')) return 'type-นำเสนอ';
return 'type-อื่นๆ';
}
function onTypeInput(input) {
const wrap = input.parentElement.querySelector('.type-preview-wrap');
const v = input.value.trim();
if (v) {
wrap.innerHTML = `<span class="type-badge ${typeBadgeClass(v)}">${v}</span>`;
} else {
wrap.innerHTML = '';
}
}
function addDateBlock() {
dateBlockCount++;
const id = 'db-' + dateBlockCount;
const today = new Date().toLocaleDateString('th-TH', { year: 'numeric', month: '2-digit', day: '2-digit' });
const container = document.getElementById('date-blocks-container');
const block = document.createElement('div');
block.className = 'date-block';
block.id = id;
block.innerHTML = `
<div class="date-row">
<label>วันที่</label>
<input type="date" class="input-date" value="${new Date().toISOString().split('T')[0]}">
<button class="btn-secondary" onclick="addRow('${id}')">+ เพิ่มแถว</button>
<button class="btn-danger-icon" title="ลบวันที่นี้" onclick="removeDateBlock('${id}')">✕</button>
</div>
<table class="schedule-table">
<thead>
<tr>
<th style="width:100px">เวลาเริ่มต้น</th>
<th style="width:100px">เวลาสิ้นสุด</th>
<th style="width:150px">ประเภท</th>
<th>หัวข้อ</th>
<th style="width:220px">วิทยากร</th>
<th style="width:40px"></th>
</tr>
</thead>
<tbody id="${id}-tbody">
<tr class="empty-row" id="${id}-empty">
<td colspan="6">ยังไม่มีรายการ — กด <strong>+ เพิ่มแถว</strong> เพื่อเพิ่ม</td>
</tr>
</tbody>
</table>
`;
container.appendChild(block);
}
let rowCount = 0;
function addRow(blockId) {
rowCount++;
const rid = 'row-' + rowCount;
const tbody = document.getElementById(blockId + '-tbody');
const empty = document.getElementById(blockId + '-empty');
if (empty) empty.remove();
const tr = document.createElement('tr');
tr.id = rid;
tr.innerHTML = `
<td><input type="text" class="tbl-input tbl-input-time" list="time-list" placeholder="08:00" oninput="syncTime(this, '${rid}')"></td>
<td><input type="text" class="tbl-input tbl-input-time" list="time-list" placeholder="09:00" id="${rid}-totime"></td>
<td>
<input type="text" class="tbl-input" list="topic-types-list" placeholder="เลือกหรือพิมพ์เอง" oninput="onTypeInput(this)">
<div class="type-preview-wrap"></div>
<div class="hint">เลือกจากรายการหรือพิมพ์เอง</div>
</td>
<td><input type="text" class="tbl-input" placeholder="หัวข้อ / รายละเอียด"></td>
<td>
<div id="${rid}-lecturers"></div>
<button class="btn-text" onclick="addLecturer('${rid}')">+ วิทยากร</button>
</td>
<td style="text-align:center;">
<button class="btn-danger-icon" title="ลบแถว" onclick="removeRow('${rid}', '${blockId}')">✕</button>
</td>
`;
tbody.appendChild(tr);
}
function syncTime(fromInput, rid) {
// auto-fill เวลาสิ้นสุดถ้ายังว่าง
const toInput = document.getElementById(rid + '-totime');
if (toInput && !toInput.value) {
const [h, m] = (fromInput.value || '').split(':').map(Number);
if (!isNaN(h)) {
const endH = h + 1;
toInput.value = `${String(endH).padStart(2,'0')}:${String(m||0).padStart(2,'0')}`;
}
}
}
let lecCount = 0;
function addLecturer(rowId) {
lecCount++;
const lid = 'lec-' + lecCount;
const container = document.getElementById(rowId + '-lecturers');
const div = document.createElement('div');
div.className = 'lec-row';
div.id = lid;
div.innerHTML = `
<input type="text" class="tbl-input" placeholder="ชื่อวิทยากร" style="width:130px">
<div class="radio-group">
<label><input type="radio" name="${lid}-type" value="inside" checked> ภายใน</label>
<label><input type="radio" name="${lid}-type" value="outside"> ภายนอก</label>
</div>
<button class="btn-danger-icon" style="font-size:14px" title="ลบวิทยากร" onclick="removeLec('${lid}')">✕</button>
`;
container.appendChild(div);
}
function removeLec(id) {
document.getElementById(id)?.remove();
}
function removeRow(rowId, blockId) {
document.getElementById(rowId)?.remove();
const tbody = document.getElementById(blockId + '-tbody');
if (!tbody) return;
const rows = tbody.querySelectorAll('tr:not(.empty-row)');
if (rows.length === 0) {
const empty = document.createElement('tr');
empty.className = 'empty-row';
empty.id = blockId + '-empty';
empty.innerHTML = '<td colspan="6">ยังไม่มีรายการ — กด <strong>+ เพิ่มแถว</strong> เพื่อเพิ่ม</td>';
tbody.appendChild(empty);
}
}
function removeDateBlock(id) {
if (confirm('ต้องการลบวันที่นี้และรายการทั้งหมดใช่ไหม?')) {
document.getElementById(id)?.remove();
}
}
// เพิ่ม demo block เริ่มต้น 1 วัน
addDateBlock();
// เพิ่ม demo row
setTimeout(() => {
addRow('db-1');
// fill demo data
const rows = document.querySelectorAll('#db-1-tbody tr:not(.empty-row)');
if (rows[0]) {
const inputs = rows[0].querySelectorAll('input');
inputs[0].value = '09:00';
inputs[1].value = '12:00';
inputs[2].value = 'บรรยาย';
onTypeInput(inputs[2]);
inputs[3].value = 'การเรียนรู้เชิงสร้างสรรค์ในยุคดิจิทัล';
}
addRow('db-1');
setTimeout(() => {
const rows2 = document.querySelectorAll('#db-1-tbody tr:not(.empty-row)');
if (rows2[1]) {
const inputs = rows2[1].querySelectorAll('input');
inputs[0].value = '13:00';
inputs[1].value = '16:00';
inputs[2].value = 'แบ่งกลุ่มปฏิบัติ';
onTypeInput(inputs[2]);
inputs[3].value = 'Workshop: ออกแบบกิจกรรมการเรียนรู้';
addLecturer('row-2');
setTimeout(() => {
const lec = document.querySelector('#row-2-lecturers .lec-row input');
if (lec) lec.value = 'ผศ.ดร.สมชาย ใจดี';
}, 0);
}
}, 0);
}, 0);
</script>
</body>
</html>