Jadwal 2 mode, reset di di box, test button ampli on, dan bel manual

This commit is contained in:
2025-12-21 23:10:59 +08:00
parent cd4ece14b3
commit 034ad6f9a3
3 changed files with 622 additions and 176 deletions

View File

@@ -515,6 +515,35 @@
.th-desc {
min-width: 150px;
}
/* Day tabs */
.day-tab {
background-color: transparent;
color: #64748b;
border: none;
cursor: pointer;
transition: all 0.2s;
}
.day-tab:hover {
background-color: #e2e8f0;
}
.day-tab.active {
background-color: #2563eb;
color: white;
}
.day-tab[data-day="0"],
.day-tab[data-day="6"] {
color: #dc2626;
}
.day-tab[data-day="0"].active,
.day-tab[data-day="6"].active {
background-color: #dc2626;
color: white;
}
</style>
</head>
@@ -584,7 +613,7 @@
<!-- Tab: Jadwal -->
<div id="tab-jadwal" class="tab-content block">
<div class="flex justify-between items-center mb-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-bold text-slate-800">Jadwal</h2>
<div class="flex gap-2">
<button onclick="tambah()" class="btn btn-green small"> Tambah</button>
@@ -592,6 +621,26 @@
</div>
</div>
<!-- Day Tabs (only shown in 7-day mode) -->
<div id="dayTabsContainer" class="hidden mb-4">
<div class="flex flex-wrap gap-1 bg-slate-100 p-1 rounded-lg">
<button onclick="switchDayTab(0)" class="day-tab px-3 py-2 rounded text-sm font-medium"
data-day="0">Minggu</button>
<button onclick="switchDayTab(1)" class="day-tab px-3 py-2 rounded text-sm font-medium active"
data-day="1">Senin</button>
<button onclick="switchDayTab(2)" class="day-tab px-3 py-2 rounded text-sm font-medium"
data-day="2">Selasa</button>
<button onclick="switchDayTab(3)" class="day-tab px-3 py-2 rounded text-sm font-medium"
data-day="3">Rabu</button>
<button onclick="switchDayTab(4)" class="day-tab px-3 py-2 rounded text-sm font-medium"
data-day="4">Kamis</button>
<button onclick="switchDayTab(5)" class="day-tab px-3 py-2 rounded text-sm font-medium"
data-day="5">Jumat</button>
<button onclick="switchDayTab(6)" class="day-tab px-3 py-2 rounded text-sm font-medium"
data-day="6">Sabtu</button>
</div>
</div>
<div class="bg-white rounded-xl shadow overflow-hidden">
<div class="overflow-x-auto">
<table class="w-full">
@@ -610,7 +659,7 @@
</table>
</div>
<div id="emptyJadwal" class="hidden p-8 text-center text-slate-400">
Belum ada jadwal.
Belum ada jadwal untuk hari ini.
</div>
</div>
</div>
@@ -629,14 +678,33 @@
</div>
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="font-semibold text-lg mb-4">📅 Hari Libur</h3>
<div class="flex items-center gap-3 bg-slate-50 p-3 rounded border">
<h3 class="font-semibold text-lg mb-4">📅 Mode Jadwal</h3>
<div class="mb-4">
<label class="block text-sm font-medium mb-2 text-slate-700">Pilih Mode:</label>
<select id="scheduleMode" class="w-full border rounded p-2" onchange="onScheduleModeChange()">
<option value="0">1 Hari (Sama Setiap Hari)</option>
<option value="1">7 Hari (Berbeda Tiap Hari)</option>
</select>
</div>
<div id="skipSundayContainer" class="flex items-center gap-3 bg-slate-50 p-3 rounded border mb-3">
<input type="checkbox" id="skipSunday" style="width: 1.25rem; height: 1.25rem;">
<label for="skipSunday" class="font-medium cursor-pointer select-none text-slate-700">Libur hari Minggu
(Bel mati)</label>
</div>
<div class="mt-3 text-right">
<button onclick="saveSkipSunday()" class="btn btn-blue">Simpan</button>
<p class="text-xs text-slate-500 mb-3" id="scheduleModeHint">Mode 1 Hari: Jadwal sama setiap hari. Centang
opsi di atas untuk menonaktifkan bel di hari Minggu.</p>
<div class="text-right">
<button onclick="saveScheduleMode()" class="btn btn-blue">Simpan Mode</button>
</div>
</div>
<div class="bg-white p-6 rounded-xl shadow">
<h3 class="font-semibold text-lg mb-4">🔔 Track Bel Manual</h3>
<p class="text-sm text-slate-500 mb-3">Track MP3 yang diputar saat tombol TEST ditekan lama (2 detik)</p>
<div class="flex gap-2">
<input id="testTrack" type="number" min="1" max="9999" placeholder="1000" style="width: 100px;">
<button onclick="saveTestTrack()" class="btn btn-blue">Simpan</button>
<button onclick="previewTestTrack()" class="btn btn-green">▶ Test</button>
</div>
</div>
@@ -746,6 +814,10 @@
// --- State & Auth ---
let jadwal = [];
let isLoggedIn = false;
let currentScheduleMode = 0; // 0 = 1-hari, 1 = 7-hari
let currentDayTab = 1; // Default to Senin (1)
const dayNames = ['M', 'S', 'S', 'R', 'K', 'J', 'S']; // Minggu, Senin, Selasa, Rabu, Kamis, Jumat, Sabtu
const dayLabels = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
async function login(event) {
event.preventDefault();
@@ -792,20 +864,50 @@
function render() {
const body = document.getElementById('body');
const empty = document.getElementById('emptyJadwal');
const dayTabsContainer = document.getElementById('dayTabsContainer');
body.innerHTML = '';
if (!jadwal.length) {
// Show/hide day tabs based on mode
if (currentScheduleMode === 1) {
dayTabsContainer.classList.remove('hidden');
} else {
dayTabsContainer.classList.add('hidden');
}
// Filter jadwal based on mode
let filteredJadwal = [];
if (currentScheduleMode === 1) {
// 7-day mode: show ALL schedules, mark which ones are active for this day
jadwal.forEach((j, i) => {
const days = j.days !== undefined ? j.days : 0x7F;
const isActiveToday = (days & (1 << currentDayTab)) !== 0;
filteredJadwal.push({ ...j, originalIndex: i, isActiveToday: isActiveToday });
});
} else {
// 1-day mode: show all with global enabled
filteredJadwal = jadwal.map((j, i) => ({ ...j, originalIndex: i, isActiveToday: j.enabled !== false }));
}
if (!filteredJadwal.length) {
empty.classList.remove('hidden');
return;
}
empty.classList.add('hidden');
jadwal.forEach((j, i) => {
filteredJadwal.forEach((j, displayIndex) => {
const tr = document.createElement('tr');
tr.className = 'hover:bg-slate-50';
tr.dataset.originalIndex = j.originalIndex;
// In 7-day mode, checkbox controls per-day activation (days bit)
// In 1-day mode, checkbox controls global enabled field
const checkboxTitle = currentScheduleMode === 1
? `Aktif di ${dayLabels[currentDayTab]}`
: 'Enabled';
tr.innerHTML = `
<td class="text-center text-slate-500 col-index">${i + 1}</td>
<td class="text-center"><input type="checkbox" ${j.enabled !== false ? 'checked' : ''} style="width:1.2rem; height:1.2rem;"></td>
<td class="text-center text-slate-500 col-index">${displayIndex + 1}</td>
<td class="text-center"><input type="checkbox" data-enabled ${j.isActiveToday ? 'checked' : ''} title="${checkboxTitle}" style="width:1.2rem; height:1.2rem;"></td>
<td><input type="number" min="0" max="23" value="${j.jam}"></td>
<td><input type="number" min="0" max="59" value="${j.menit}"></td>
<td><input type="number" min="1" value="${j.track || j.trackStart || 1}"></td>
@@ -813,49 +915,91 @@
<td class="text-center">
<div class="flex justify-center gap-1">
<button onclick="preview(${j.track || j.trackStart || 1})" class="btn btn-green small">▶</button>
<button onclick="del(${i})" class="btn btn-red small">✕</button>
<button onclick="delByIndex(${j.originalIndex})" class="btn btn-red small">✕</button>
</div>
</td>`;
body.appendChild(tr);
});
}
function tambah() {
if (!isLoggedIn) return;
if (jadwal.length >= 20) return alert('Maksimal 20 jadwal');
jadwal.push({ jam: 7, menit: 0, track: 1, desc: 'Bel Masuk', enabled: true });
function switchDayTab(day) {
currentDayTab = day;
// Update active state
document.querySelectorAll('.day-tab').forEach(btn => {
btn.classList.remove('active');
if (parseInt(btn.dataset.day) === day) {
btn.classList.add('active');
}
});
render();
}
function del(i) {
function tambah() {
if (!isLoggedIn) return;
if (jadwal.length >= 20) return alert('Maksimal 20 jadwal');
// In 7-day mode, set only current day; in 1-day mode, set all days
const days = currentScheduleMode === 1 ? (1 << currentDayTab) : 0x7F;
jadwal.push({ jam: 7, menit: 0, track: 1, desc: 'Bel Masuk', enabled: true, days: days });
render();
}
function delByIndex(originalIndex) {
if (!isLoggedIn) return;
if (confirm('Hapus jadwal ini?')) {
jadwal.splice(i, 1);
jadwal.splice(originalIndex, 1);
render();
}
}
// Keep old del function for compatibility
function del(i) {
delByIndex(i);
}
async function simpan() {
if (!isLoggedIn) return;
const rows = document.querySelectorAll('#body tr');
const arr = [];
rows.forEach(r => {
const inputs = r.querySelectorAll('input');
if (inputs.length >= 5) {
arr.push({
jam: parseInt(inputs[1].value || 0),
menit: parseInt(inputs[2].value || 0),
track: parseInt(inputs[3].value || 1),
desc: inputs[4].value || '',
enabled: inputs[0].checked
});
// Update jadwal array based on displayed rows
rows.forEach(tr => {
const originalIndex = parseInt(tr.dataset.originalIndex);
if (isNaN(originalIndex) || originalIndex >= jadwal.length) return;
const enabledCb = tr.querySelector('input[data-enabled]');
const numInputs = tr.querySelectorAll('input[type="number"]');
const textInput = tr.querySelector('input[type="text"]');
if (numInputs.length >= 3 && enabledCb) {
jadwal[originalIndex].jam = parseInt(numInputs[0].value || 0);
jadwal[originalIndex].menit = parseInt(numInputs[1].value || 0);
jadwal[originalIndex].track = parseInt(numInputs[2].value || 1);
jadwal[originalIndex].desc = textInput ? textInput.value : '';
if (currentScheduleMode === 1) {
// 7-day mode: checkbox controls per-day bit in days field
let currentDays = jadwal[originalIndex].days !== undefined ? jadwal[originalIndex].days : 0x7F;
if (enabledCb.checked) {
// Add current day bit
currentDays |= (1 << currentDayTab);
} else {
// Remove current day bit
currentDays &= ~(1 << currentDayTab);
}
jadwal[originalIndex].days = currentDays;
// Keep enabled always true in 7-day mode (days field controls per-day)
jadwal[originalIndex].enabled = true;
} else {
// 1-day mode: checkbox controls global enabled
jadwal[originalIndex].enabled = enabledCb.checked;
}
}
});
await fetch('/api/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(arr)
body: JSON.stringify(jadwal)
});
alert('✅ Jadwal Tersimpan!');
loadJadwal();
@@ -872,7 +1016,49 @@
document.getElementById('mobileTitle').textContent = d.schoolName;
}
document.getElementById('skipSunday').checked = d.skipSunday || false;
document.getElementById('testTrack').value = d.testTrack || 1000;
if (d.user) document.getElementById('userInput').placeholder = d.user;
// Load schedule mode
currentScheduleMode = d.scheduleMode || 0;
document.getElementById('scheduleMode').value = currentScheduleMode;
updateScheduleModeUI();
render(); // Re-render to show/hide days column
}
function updateScheduleModeUI() {
const skipSundayContainer = document.getElementById('skipSundayContainer');
const hint = document.getElementById('scheduleModeHint');
if (currentScheduleMode === 1) {
// Mode 7-hari: hide skipSunday option
skipSundayContainer.classList.add('hidden');
hint.textContent = 'Mode 7 Hari: Pilih hari aktif untuk setiap jadwal di tabel Jadwal.';
} else {
// Mode 1-hari: show skipSunday option
skipSundayContainer.classList.remove('hidden');
hint.textContent = 'Mode 1 Hari: Jadwal sama setiap hari. Centang opsi di atas untuk menonaktifkan bel di hari Minggu.';
}
}
function onScheduleModeChange() {
currentScheduleMode = parseInt(document.getElementById('scheduleMode').value);
updateScheduleModeUI();
render(); // Re-render to show/hide days column
}
async function saveScheduleMode() {
const mode = parseInt(document.getElementById('scheduleMode').value);
const skip = document.getElementById('skipSunday').checked;
await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ scheduleMode: mode, skipSunday: skip })
});
currentScheduleMode = mode;
alert('Mode Jadwal Disimpan!');
}
async function saveName() {
@@ -897,6 +1083,21 @@
alert('Pengaturan Disimpan');
}
async function saveTestTrack() {
const track = parseInt(document.getElementById('testTrack').value) || 1000;
await fetch('/api/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ testTrack: track })
});
alert('Track Test Disimpan: ' + track);
}
async function previewTestTrack() {
const track = parseInt(document.getElementById('testTrack').value) || 1000;
await fetch(`/api/play?track=${track}`);
}
async function syncTime() {
const now = new Date();
const epoch = Math.floor((now.getTime() - now.getTimezoneOffset() * 60000) / 1000);