TKK_E32230273/room.php

454 lines
20 KiB
PHP

<?php
include 'koneksi.php';
// Create table if not exists
$pdo->exec("CREATE TABLE IF NOT EXISTS room_restrictions (
id_restriction INT AUTO_INCREMENT PRIMARY KEY,
day_of_week VARCHAR(20) NOT NULL,
time_from TIME NOT NULL,
time_to TIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
// Fetch all restrictions
$stmt = $pdo->query("SELECT * FROM room_restrictions ORDER BY FIELD(day_of_week, 'Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sunday','Every Day'), time_from ASC");
$restrictions = $stmt->fetchAll();
include 'header.php';
?>
<div class="main-panel">
<div class="content-wrapper">
<div class="row">
<div class="col-md-12 grid-margin">
<div class="row">
<div class="col-12 col-xl-8 mb-4 mb-xl-0">
<h3 class="font-weight-bold">Room</h3>
<h6 class="font-weight-normal mb-0">Manage room access restrictions, <span class="text-primary">Set restricted hours for the room!</span></h6>
</div>
<div class="col-12 col-xl-4">
<div class="justify-content-end d-flex flex-wrap" style="gap:15px; align-items:center;">
<div class="justify-content-end d-flex">
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== CARD 1: FORM ==================== -->
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card" style="border:1px solid #e5e9f0;box-shadow:none;border-radius:12px;">
<div class="card-body" style="padding:28px;">
<p class="card-title mb-0">Add Restriction</p>
<p class="text-muted mt-1 mb-4" style="font-size:12px;">Define the time period when the room cannot be accessed via fingerprint.</p>
<form id="formRestriction">
<input type="hidden" name="id_restriction" id="edit_id_restriction" value="">
<div class="row">
<!-- Day Selection -->
<div class="col-md-4 mb-3">
<label class="frm-lbl">Day</label>
<select name="day_of_week" id="input_day" class="form-control form-control-sm" required style="border-color:#e5e9f0; height:38px;">
<option value="">--- Select Day ---</option>
<option value="Monday">Monday</option>
<option value="Tuesday">Tuesday</option>
<option value="Wednesday">Wednesday</option>
<option value="Thursday">Thursday</option>
<option value="Friday">Friday</option>
<option value="Saturday">Saturday</option>
<option value="Sunday">Sunday</option>
<option value="Every Day">Every Day (*)</option>
</select>
</div>
<!-- From Time -->
<div class="col-md-4 mb-3">
<label class="frm-lbl">From</label>
<input type="hidden" name="time_from" id="input_time_from">
<div class="clock-picker-group form-control form-control-sm" style="border-color:#e5e9f0; height:38px; padding: 0 10px;">
<select id="from_hour" class="clock-picker-select" required>
<option value="">--</option>
<?php for($h=0;$h<=23;$h++): ?>
<option value="<?= str_pad($h,2,'0',STR_PAD_LEFT) ?>"><?= str_pad($h,2,'0',STR_PAD_LEFT) ?></option>
<?php endfor; ?>
</select>
<span class="clock-picker-separator">:</span>
<select id="from_minute" class="clock-picker-select" required>
<option value="">--</option>
<?php for($m=0;$m<60;$m++): ?>
<option value="<?= str_pad($m,2,'0',STR_PAD_LEFT) ?>"><?= str_pad($m,2,'0',STR_PAD_LEFT) ?></option>
<?php endfor; ?>
</select>
</div>
</div>
<!-- To Time -->
<div class="col-md-4 mb-3">
<label class="frm-lbl">To</label>
<input type="hidden" name="time_to" id="input_time_to">
<div class="clock-picker-group form-control form-control-sm" style="border-color:#e5e9f0; height:38px; padding: 0 10px;">
<select id="to_hour" class="clock-picker-select" required>
<option value="">--</option>
<?php for($h=0;$h<=23;$h++): ?>
<option value="<?= str_pad($h,2,'0',STR_PAD_LEFT) ?>"><?= str_pad($h,2,'0',STR_PAD_LEFT) ?></option>
<?php endfor; ?>
</select>
<span class="clock-picker-separator">:</span>
<select id="to_minute" class="clock-picker-select" required>
<option value="">--</option>
<?php for($m=0;$m<60;$m++): ?>
<option value="<?= str_pad($m,2,'0',STR_PAD_LEFT) ?>"><?= str_pad($m,2,'0',STR_PAD_LEFT) ?></option>
<?php endfor; ?>
</select>
</div>
</div>
</div>
<div class="d-flex align-items-center" style="gap:10px;">
<button type="submit" class="btn btn-primary btn-sm" id="btnSaveRestriction" style="padding:8px 20px; font-weight:600;">
<i class="mdi mdi-content-save mr-1"></i>Save
</button>
<button type="button" class="btn btn-light btn-sm" id="btnCancelEdit" style="display:none; padding:8px 20px; font-weight:600;" onclick="cancelEdit()">
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- ==================== CARD 2: TABLE ==================== -->
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card" style="border:1px solid #e5e9f0;box-shadow:none;border-radius:12px;">
<div class="card-body" style="padding:28px;">
<p class="card-title mb-3">Restricted Hours</p>
<div class="table-responsive">
<table class="table table-borderless" id="restrictionTable">
<thead>
<tr>
<th style="width:50px;">No</th>
<th>Day</th>
<th>From</th>
<th>To</th>
<th style="width:180px; text-align:center;">Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($restrictions)): ?>
<tr id="emptyRow">
<td colspan="5" style="text-align:center; padding:30px 10px; color:#999; font-size:13px;">
<i class="mdi mdi-clock-alert-outline" style="font-size:32px; display:block; margin-bottom:6px; color:#ddd;"></i>
No restrictions have been set yet
</td>
</tr>
<?php else:
$no = 1;
foreach ($restrictions as $row):
$sc = ($no % 2 == 1) ? 'row-odd' : 'row-even';
$dayLabel = $row['day_of_week'];
if ($dayLabel === 'Every Day') $dayLabel = 'Every Day (*)';
?>
<tr class="<?= $sc; ?>" id="row_<?= $row['id_restriction']; ?>">
<td style="vertical-align:middle; font-weight:600; color:#555;"><?= $no++; ?></td>
<td style="vertical-align:middle;">
<span style="font-weight:500; font-size:14px; color:#212529;"><?= htmlspecialchars($dayLabel); ?></span>
</td>
<td style="vertical-align:middle; font-weight:500;">
<span style="font-family:'Courier New',monospace; font-size:13px; color:#4b49ac; font-weight:bold;"><?= substr($row['time_from'], 0, 5); ?></span>
</td>
<td style="vertical-align:middle; font-weight:500;">
<span style="font-family:'Courier New',monospace; font-size:13px; color:#4b49ac; font-weight:bold;"><?= substr($row['time_to'], 0, 5); ?></span>
</td>
<td style="vertical-align:middle; text-align:center;">
<button class="btn-edit-action btn-edit-row" id="btnEdit_<?= $row['id_restriction']; ?>" title="Edit" onclick="editRestriction('<?= $row['id_restriction']; ?>', '<?= htmlspecialchars($row['day_of_week']); ?>', '<?= substr($row['time_from'], 0, 5); ?>', '<?= substr($row['time_to'], 0, 5); ?>')">
<i class="mdi mdi-pencil mr-1"></i>Edit
</button>
<button class="btn-delete-action ml-2" title="Delete" onclick="openDeleteModal('<?= $row['id_restriction']; ?>', '<?= htmlspecialchars($row['day_of_week']); ?>')">
<i class="mdi mdi-delete"></i>
</button>
</td>
</tr>
<?php
endforeach;
endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== MODAL: DELETE ==================== -->
<div class="modal fade" id="modalDeleteRestriction" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content mdl-card">
<form id="formDeleteRestriction">
<div class="modal-body p-4" style="text-align: left;">
<input type="hidden" name="id_restriction" id="delete_id_restriction">
<h5 class="font-weight-bold mb-3">Delete Restriction</h5>
<p style="font-size:14px;color:#666;margin-bottom:20px;">Are you sure you want to delete the restriction for <strong id="delete_restriction_name"></strong>?</p>
<div class="custom-control custom-checkbox mb-4" style="display:flex; align-items:center;">
<input type="checkbox" class="custom-control-input delete-confirm-cb" id="deleteConfirmCheckRestriction" required>
<label class="custom-control-label" for="deleteConfirmCheckRestriction" style="font-size:14px; color:#1e1f23; padding-top:2px;">I understand that this restriction will be permanently removed</label>
</div>
<div class="d-flex justify-content-end" style="gap:8px;">
<button type="button" class="btn btn-light font-weight-bold" style="padding:8px 18px;font-size:13px;" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-danger font-weight-bold" style="padding:8px 18px;font-size:13px;" id="btnDeleteRestriction" disabled>Delete</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
(function(){
var cb = document.getElementById('deleteConfirmCheckRestriction');
var btn = document.getElementById('btnDeleteRestriction');
if(cb && btn){
cb.addEventListener('change', function(){ btn.disabled = !this.checked; });
}
$('#modalDeleteRestriction').on('hidden.bs.modal', function(){ if(cb){cb.checked=false;} if(btn){btn.disabled=true;} });
})();
</script>
<!-- ========================== CSS ========================== -->
<style>
/* ── TABLE ── */
#restrictionTable { border-collapse: collapse; width: 100%; margin-top: 10px; }
#restrictionTable thead th { border-bottom: 1px solid #e2e8f0 !important; font-size: 13px; font-weight: 600; color: #555; text-transform: uppercase; letter-spacing: 0.5px; padding: 20px 24px !important; }
#restrictionTable tbody tr td { border-bottom: none !important; padding: 20px 24px !important; }
#restrictionTable tbody tr.row-odd > td { background-color: #fafafa !important; }
#restrictionTable tbody tr.row-even > td { background-color: #ffffff !important; }
#restrictionTable tbody tr.row-highlight td { background-color: #f0f4ff !important; }
.frm-lbl { font-size: 12px; font-weight: 600; color: #555; margin-bottom: 3px; display: block; }
/* ── CLOCK PICKER ── */
.clock-picker-group {
display: flex;
align-items: center;
gap: 4px;
}
.clock-picker-select {
height: 28px;
width: 44px;
border: none;
background: transparent;
font-size: 14px;
font-weight: 400;
color: #495057;
text-align: center;
cursor: pointer;
outline: none;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
padding: 0 2px;
}
.clock-picker-select:focus {
background: transparent;
}
.clock-picker-separator {
font-size: 14px;
font-weight: 600;
color: #495057;
line-height: 28px;
}
/* ── MODALS ── */
.mdl-card { border: none; border-radius: 6px; box-shadow: 0 8px 30px rgba(0,0,0,0.1); }
.delete-confirm-cb:checked ~ .custom-control-label::before {
background-color: #ff4747 !important;
border-color: #ff4747 !important;
}
.btn-edit-action {
font-size: 13px; font-weight: 500; color: #333; background: #fff;
border: 1px solid #d4d4d8; border-radius: 6px; padding: 6px 16px;
cursor: pointer; transition: all .15s; display: inline-flex; align-items: center;
line-height: 1;
}
.btn-edit-action:hover { background: #f5f5f5; border-color: #bbb; color: #333; text-decoration: none; }
.btn-delete-action {
font-size: 13px; font-weight: 500; color: #fff; background: #ff4747;
border: 1px solid #ff4747; border-radius: 6px; padding: 6px 16px;
cursor: pointer; transition: all .15s; display: inline-flex; align-items: center;
line-height: 1;
}
.btn-delete-action:hover { background: #e03e3e; border-color: #e03e3e; color: #fff; text-decoration: none; }
</style>
<!-- content-wrapper ends -->
<?php include 'footer.php'; ?>
<?php include 'copy.php'; ?>
<!-- ========================== JS ========================== -->
<script>
var isEditing = false;
// ── CLOCK PICKER HELPERS ──
function syncClockToHidden(prefix) {
var h = document.getElementById(prefix + '_hour').value;
var m = document.getElementById(prefix + '_minute').value;
if (!h || !m) { document.getElementById('input_time_' + prefix).value = ''; return; }
document.getElementById('input_time_' + prefix).value = String(h).padStart(2,'0') + ':' + m;
}
function set24hToClock(prefix, time24) {
if (!time24) return;
var parts = time24.split(':');
document.getElementById(prefix + '_hour').value = parts[0];
document.getElementById(prefix + '_minute').value = parts[1];
syncClockToHidden(prefix);
}
// Attach change listeners to sync hidden fields
['from', 'to'].forEach(function(prefix) {
['_hour', '_minute'].forEach(function(suffix) {
document.getElementById(prefix + suffix).addEventListener('change', function() {
syncClockToHidden(prefix);
});
});
});
// Save / Update restriction
document.getElementById('formRestriction').addEventListener('submit', function(e) {
e.preventDefault();
// Sync clock pickers to hidden inputs
syncClockToHidden('from');
syncClockToHidden('to');
var timeFrom = document.getElementById('input_time_from').value;
var timeTo = document.getElementById('input_time_to').value;
if (!timeFrom || !timeTo) {
if (typeof showCustomAlert === 'function') showCustomAlert('Incomplete', 'Please select both From and To times.', 'error');
else alert('Please select both From and To times.');
return;
}
var btn = document.getElementById('btnSaveRestriction');
btn.disabled = true;
btn.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Saving...';
var fd = new FormData(this);
var url = fd.get('id_restriction') ? 'update_room_restriction.php' : 'save_room_restriction.php';
fetch(url, { method: 'POST', body: fd })
.then(function(r) { return r.json(); })
.then(function(d) {
btn.disabled = false;
btn.innerHTML = '<i class="mdi mdi-content-save mr-1"></i>Save';
if (d.status === 'success') {
if (typeof showCustomAlert === 'function') showCustomAlert('Success', d.message, 'success');
setTimeout(function() { location.reload(); }, 1000);
} else {
if (typeof showCustomAlert === 'function') showCustomAlert('Failed', d.message || 'Failed to save data.', 'error');
else alert(d.message || 'Failed to save data.');
}
}).catch(function() {
btn.disabled = false;
btn.innerHTML = '<i class="mdi mdi-content-save mr-1"></i>Save';
alert('Connection to server failed.');
});
});
// Edit - populate form
function editRestriction(id, day, timeFrom, timeTo) {
// If already editing this row, cancel
if (isEditing && document.getElementById('edit_id_restriction').value === id) {
cancelEdit();
return;
}
document.getElementById('edit_id_restriction').value = id;
document.getElementById('input_day').value = day;
// Populate clock pickers from 24h values
set24hToClock('from', timeFrom);
set24hToClock('to', timeTo);
document.getElementById('btnCancelEdit').style.display = 'inline-block';
isEditing = true;
// Scroll to form
window.scrollTo({ top: 0, behavior: 'smooth' });
// Highlight the row being edited
document.querySelectorAll('#restrictionTable tbody tr').forEach(function(tr) {
tr.classList.remove('row-highlight');
});
var editRow = document.getElementById('row_' + id);
if (editRow) {
editRow.classList.add('row-highlight');
}
}
// Cancel edit
function cancelEdit() {
document.getElementById('edit_id_restriction').value = '';
document.getElementById('input_day').value = '';
document.getElementById('input_time_from').value = '';
document.getElementById('input_time_to').value = '';
// Reset clock pickers
['from', 'to'].forEach(function(prefix) {
document.getElementById(prefix + '_hour').value = '';
document.getElementById(prefix + '_minute').value = '';
});
document.getElementById('btnCancelEdit').style.display = 'none';
isEditing = false;
// Remove row highlight
document.querySelectorAll('#restrictionTable tbody tr').forEach(function(tr) {
tr.classList.remove('row-highlight');
});
}
// Delete
function openDeleteModal(id, dayName) {
document.getElementById('delete_id_restriction').value = id;
document.getElementById('delete_restriction_name').innerText = dayName;
$('#modalDeleteRestriction').modal('show');
}
document.getElementById('formDeleteRestriction').addEventListener('submit', function(e) {
e.preventDefault();
var btn = document.getElementById('btnDeleteRestriction');
btn.disabled = true;
btn.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Deleting...';
var fd = new FormData(this);
fetch('delete_room_restriction.php', { method: 'POST', body: fd })
.then(function(r) { return r.json(); })
.then(function(d) {
btn.disabled = false;
btn.innerHTML = 'Delete';
if (d.status === 'success') {
$('#modalDeleteRestriction').modal('hide');
if (typeof showCustomAlert === 'function') showCustomAlert('Success', d.message, 'success');
setTimeout(function() { location.reload(); }, 1000);
} else {
if (typeof showCustomAlert === 'function') showCustomAlert('Failed', d.message || 'Failed to delete.', 'error');
else alert(d.message || 'Failed to delete.');
}
}).catch(function() {
btn.disabled = false;
btn.innerHTML = 'Delete';
alert('Connection to server failed.');
});
});
</script>