Jadwal 2 mode, reset di di box, test button ampli on, dan bel manual
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user