555 lines
24 KiB
PHP
555 lines
24 KiB
PHP
<?php $s = get_system_settings(); ?>
|
|
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/main.min.css' rel='stylesheet' />
|
|
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.8/index.global.min.js'></script>
|
|
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h3 class="fw-bold text-secondary"><?php echo __('appointments'); ?></h3>
|
|
<div>
|
|
<button class="btn btn-outline-secondary shadow-sm me-2" onclick="printAppointments()">
|
|
<i class="bi bi-printer me-1"></i> Print Daily List
|
|
</button>
|
|
<button class="btn btn-primary shadow-sm" onclick="showCreateModal()">
|
|
<i class="bi bi-calendar-plus me-1"></i> <?php echo __('book_appointment'); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filters -->
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-body">
|
|
<div class="row g-3">
|
|
<div class="col-md-4">
|
|
<label class="form-label small text-muted"><?php echo __('doctor'); ?> (Filter)</label>
|
|
<select id="doctorFilter" class="form-select bg-light">
|
|
<option value=""><?php echo __('all'); ?> <?php echo __('doctors'); ?></option>
|
|
<?php foreach ($all_doctors as $d): ?>
|
|
<option value="<?php echo $d['id']; ?>"><?php echo htmlspecialchars($d['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<div class="d-flex align-items-end h-100 pb-1 flex-wrap">
|
|
<div class="d-flex align-items-center me-3 mb-2">
|
|
<div class="badge bg-primary me-2" style="width: 15px; height: 15px; border-radius: 50%;"> </div>
|
|
<small class="text-muted">Scheduled</small>
|
|
</div>
|
|
<div class="d-flex align-items-center me-3 mb-2">
|
|
<div class="badge bg-success me-2" style="width: 15px; height: 15px; border-radius: 50%;"> </div>
|
|
<small class="text-muted">Completed</small>
|
|
</div>
|
|
<div class="d-flex align-items-center me-3 mb-2">
|
|
<div class="badge bg-danger me-2" style="width: 15px; height: 15px; border-radius: 50%;"> </div>
|
|
<small class="text-muted">Cancelled</small>
|
|
</div>
|
|
<div class="d-flex align-items-center me-3 mb-2">
|
|
<div class="badge me-2" style="width: 15px; height: 15px; border-radius: 50%; background-color: #fd7e14;"> </div>
|
|
<small class="text-muted">Home Visit</small>
|
|
</div>
|
|
<div class="d-flex align-items-center mb-2">
|
|
<div class="badge bg-warning me-2" style="width: 15px; height: 15px; border-radius: 50%;"> </div>
|
|
<small class="text-muted">Holiday</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card shadow-sm border-0 mb-4">
|
|
<div class="card-body p-4">
|
|
<div id='calendar'></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Appointment Details/Edit Modal -->
|
|
<div class="modal fade" id="appointmentDetailsModal" tabindex="-1" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content border-0 shadow">
|
|
<div class="modal-header border-0 pb-0">
|
|
<h5 class="modal-title fw-bold text-secondary" id="modalTitle">Appointment Details</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body pt-3">
|
|
<input type="hidden" id="apt_id">
|
|
<div class="mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('patient'); ?></label>
|
|
<select id="apt_patient_id" class="form-select select2-modal-apt">
|
|
<?php foreach ($all_patients as $p): ?>
|
|
<option value="<?php echo $p['id']; ?>" data-address="<?php echo htmlspecialchars($p['address'] ?? ''); ?>"><?php echo htmlspecialchars($p['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-md-6">
|
|
<label class="form-label small text-muted">Visit Type</label>
|
|
<select id="apt_visit_type" class="form-select" onchange="toggleAddressField()">
|
|
<option value="Clinic">Clinic Visit</option>
|
|
<option value="Home">Home Visit</option>
|
|
</select>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<label class="form-label small text-muted">Provider Type</label>
|
|
<select id="apt_provider_type" class="form-select" onchange="toggleProviderField()">
|
|
<option value="Doctor">Doctor</option>
|
|
<option value="Nurse">Nurse</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mb-3 d-none" id="div_address">
|
|
<label class="form-label small text-muted">Address (For Home Visit)</label>
|
|
<textarea id="apt_address" class="form-control" rows="2" placeholder="Enter patient address..."></textarea>
|
|
</div>
|
|
|
|
<div class="mb-3" id="div_doctor">
|
|
<label class="form-label small text-muted"><?php echo __('doctor'); ?></label>
|
|
<select id="apt_doctor_id" class="form-select select2-modal-apt">
|
|
<option value="">Select Doctor</option>
|
|
<?php foreach ($all_doctors as $d): ?>
|
|
<option value="<?php echo $d['id']; ?>"><?php echo htmlspecialchars($d['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3 d-none" id="div_nurse">
|
|
<label class="form-label small text-muted"><?php echo __('nurse'); ?></label>
|
|
<select id="apt_nurse_id" class="form-select select2-modal-apt">
|
|
<option value="">Select Nurse</option>
|
|
<?php foreach ($all_nurses as $n): ?>
|
|
<option value="<?php echo $n['id']; ?>"><?php echo htmlspecialchars($n['name']); ?></option>
|
|
<?php endforeach; ?>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('date'); ?> & <?php echo __('time'); ?></label>
|
|
<input type="datetime-local" id="apt_start_time" class="form-control">
|
|
</div>
|
|
<div class="col-md-6 mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('status'); ?></label>
|
|
<select id="apt_status" class="form-select">
|
|
<option value="Scheduled">Scheduled</option>
|
|
<option value="Completed">Completed</option>
|
|
<option value="Cancelled">Cancelled</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<label class="form-label small text-muted"><?php echo __('reason'); ?></label>
|
|
<textarea id="apt_reason" class="form-control" rows="2"></textarea>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0 bg-light rounded-bottom">
|
|
<div class="d-flex justify-content-between w-100">
|
|
<button type="button" class="btn btn-outline-danger shadow-sm" id="btnDeleteApt" onclick="deleteAppointment()">
|
|
<i class="bi bi-trash"></i> <?php echo __('delete'); ?>
|
|
</button>
|
|
<div>
|
|
<button type="button" class="btn btn-secondary shadow-sm me-2" data-bs-dismiss="modal"><?php echo __('cancel'); ?></button>
|
|
<button type="button" class="btn btn-primary shadow-sm px-4" id="btnSaveApt" onclick="saveAppointment()">
|
|
<i class="bi bi-check2-circle me-1"></i> <?php echo __('save'); ?>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
var calendar;
|
|
|
|
function initAptSelect2() {
|
|
if (typeof jQuery !== 'undefined' && jQuery.fn.select2) {
|
|
$('.select2-modal-apt').each(function() {
|
|
$(this).select2({
|
|
dropdownParent: $('#appointmentDetailsModal'),
|
|
theme: 'bootstrap-5',
|
|
width: '100%'
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function toggleAddressField() {
|
|
var type = document.getElementById('apt_visit_type').value;
|
|
var div = document.getElementById('div_address');
|
|
if (type === 'Home') {
|
|
div.classList.remove('d-none');
|
|
// Auto-fill address if empty and patient selected
|
|
var addressField = document.getElementById('apt_address');
|
|
if (!addressField.value) {
|
|
var patientSelect = document.getElementById('apt_patient_id');
|
|
var selectedOption = patientSelect.options[patientSelect.selectedIndex];
|
|
if (selectedOption && selectedOption.dataset.address) {
|
|
addressField.value = selectedOption.dataset.address;
|
|
}
|
|
}
|
|
} else {
|
|
div.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function toggleProviderField() {
|
|
var type = document.getElementById('apt_provider_type').value;
|
|
var divDoc = document.getElementById('div_doctor');
|
|
var divNurse = document.getElementById('div_nurse');
|
|
|
|
if (type === 'Nurse') {
|
|
divDoc.classList.add('d-none');
|
|
divNurse.classList.remove('d-none');
|
|
// Clear doctor selection? Maybe not necessary, backend handles nulls based on logic
|
|
} else {
|
|
divDoc.classList.remove('d-none');
|
|
divNurse.classList.add('d-none');
|
|
}
|
|
// Re-validate holiday check since provider changed
|
|
validateHolidayFrontend();
|
|
}
|
|
|
|
function showCreateModal(startTime = null) {
|
|
document.getElementById('modalTitle').innerText = '<?php echo __('book_appointment'); ?>';
|
|
document.getElementById('apt_id').value = '';
|
|
document.getElementById('apt_reason').value = '';
|
|
document.getElementById('apt_status').value = 'Scheduled';
|
|
|
|
// Defaults
|
|
document.getElementById('apt_visit_type').value = 'Clinic';
|
|
document.getElementById('apt_provider_type').value = 'Doctor';
|
|
document.getElementById('apt_address').value = '';
|
|
$('#apt_nurse_id').val('').trigger('change');
|
|
|
|
toggleAddressField();
|
|
toggleProviderField();
|
|
|
|
document.getElementById('btnDeleteApt').style.display = 'none';
|
|
|
|
if (startTime && startTime instanceof Date) {
|
|
var offset = startTime.getTimezoneOffset() * 60000;
|
|
var localISOTime = (new Date(startTime.getTime() - offset)).toISOString().slice(0, 16);
|
|
document.getElementById('apt_start_time').value = localISOTime;
|
|
} else {
|
|
var now = new Date();
|
|
var offset = now.getTimezoneOffset() * 60000;
|
|
var localISOTime = (new Date(now.getTime() - offset)).toISOString().slice(0, 16);
|
|
document.getElementById('apt_start_time').value = localISOTime;
|
|
}
|
|
|
|
if (document.getElementById('doctorFilter').value) {
|
|
$('#apt_doctor_id').val(document.getElementById('doctorFilter').value).trigger('change');
|
|
}
|
|
|
|
// Initialize/reset modal
|
|
var modalEl = document.getElementById('appointmentDetailsModal');
|
|
var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
modal.show();
|
|
}
|
|
|
|
function saveAppointment() {
|
|
var id = document.getElementById('apt_id').value;
|
|
var providerType = document.getElementById('apt_provider_type').value;
|
|
|
|
var data = {
|
|
action: id ? 'update' : 'create',
|
|
id: id,
|
|
patient_id: document.getElementById('apt_patient_id').value,
|
|
doctor_id: providerType === 'Doctor' ? document.getElementById('apt_doctor_id').value : null,
|
|
nurse_id: providerType === 'Nurse' ? document.getElementById('apt_nurse_id').value : null,
|
|
visit_type: document.getElementById('apt_visit_type').value,
|
|
address: document.getElementById('apt_address').value,
|
|
start_time: document.getElementById('apt_start_time').value,
|
|
status: document.getElementById('apt_status').value,
|
|
reason: document.getElementById('apt_reason').value
|
|
};
|
|
|
|
fetch('api/appointments.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(result => {
|
|
if (result.success) {
|
|
var modalEl = document.getElementById('appointmentDetailsModal');
|
|
var modal = bootstrap.Modal.getInstance(modalEl);
|
|
modal.hide();
|
|
calendar.refetchEvents();
|
|
} else {
|
|
alert('Error: ' + (result.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fetch error:', error);
|
|
alert('An error occurred while saving the appointment. Please check console for details.');
|
|
});
|
|
}
|
|
|
|
function deleteAppointment() {
|
|
var id = document.getElementById('apt_id').value;
|
|
if (!id || !confirm('Are you sure you want to delete this appointment?')) return;
|
|
|
|
fetch('api/appointments.php', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ action: 'delete', id: id })
|
|
})
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(result => {
|
|
if (result.success) {
|
|
var modalEl = document.getElementById('appointmentDetailsModal');
|
|
var modal = bootstrap.Modal.getInstance(modalEl);
|
|
modal.hide();
|
|
calendar.refetchEvents();
|
|
} else {
|
|
alert('Error: ' + (result.error || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Fetch error:', error);
|
|
alert('An error occurred while deleting the appointment. Please check console for details.');
|
|
});
|
|
}
|
|
|
|
function printAppointments() {
|
|
var doctorId = document.getElementById('doctorFilter').value;
|
|
// Get the date that is currently focused in the calendar
|
|
var date = calendar.getDate();
|
|
// Format as YYYY-MM-DD using local time to avoid timezone shifts
|
|
var offset = date.getTimezoneOffset() * 60000;
|
|
var dateStr = (new Date(date.getTime() - offset)).toISOString().slice(0, 10);
|
|
|
|
window.open('print_appointments.php?date=' + dateStr + '&doctor_id=' + doctorId, '_blank');
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
var calendarEl = document.getElementById('calendar');
|
|
var doctorFilter = document.getElementById('doctorFilter');
|
|
|
|
calendar = new FullCalendar.Calendar(calendarEl, {
|
|
initialView: 'timeGridWeek',
|
|
headerToolbar: {
|
|
left: 'prev,next today',
|
|
center: 'title',
|
|
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
|
},
|
|
allDaySlot: true,
|
|
slotMinTime: '<?php echo substr(($s['working_hours_start'] ?? '07:00'), 0, 5) . ':00'; ?>',
|
|
slotMaxTime: '<?php echo substr(($s['working_hours_end'] ?? '21:00'), 0, 5) . ':00'; ?>',
|
|
height: 'auto',
|
|
themeSystem: 'bootstrap5',
|
|
businessHours: true,
|
|
events: function(fetchInfo, successCallback, failureCallback) {
|
|
fetch('api/appointments.php?start=' + fetchInfo.startStr + '&end=' + fetchInfo.endStr + '&doctor_id=' + doctorFilter.value)
|
|
.then(response => {
|
|
if (!response.ok) {
|
|
throw new Error('Network response was not ok');
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(data => {
|
|
// We DO NOT setOption('businessHours') here to prevent FullCalendar from re-rendering and clearing events.
|
|
successCallback(data.events);
|
|
})
|
|
.catch(error => failureCallback(error));
|
|
},
|
|
editable: false,
|
|
selectable: true,
|
|
selectOverlap: function(event) {
|
|
if (event.extendedProps && event.extendedProps.blocks_selection) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
selectAllow: function(selectInfo) {
|
|
// Also optionally check if we can select
|
|
return true;
|
|
},
|
|
select: function(info) {
|
|
showCreateModal(info.start);
|
|
calendar.unselect();
|
|
},
|
|
eventClick: function(info) {
|
|
if (info.event.extendedProps.type === 'appointment') {
|
|
var props = info.event.extendedProps;
|
|
document.getElementById('modalTitle').innerText = 'Edit Appointment';
|
|
document.getElementById('apt_id').value = info.event.id;
|
|
$('#apt_patient_id').val(props.patient_id).trigger('change');
|
|
|
|
// Set fields
|
|
var visitType = props.visit_type || 'Clinic';
|
|
document.getElementById('apt_visit_type').value = visitType;
|
|
document.getElementById('apt_address').value = props.address || '';
|
|
toggleAddressField();
|
|
|
|
var providerType = props.nurse_id ? 'Nurse' : 'Doctor';
|
|
document.getElementById('apt_provider_type').value = providerType;
|
|
toggleProviderField();
|
|
|
|
if (providerType === 'Doctor') {
|
|
$('#apt_doctor_id').val(props.doctor_id).trigger('change');
|
|
} else {
|
|
$('#apt_nurse_id').val(props.nurse_id).trigger('change');
|
|
}
|
|
|
|
var start = info.event.start;
|
|
var offset = start.getTimezoneOffset() * 60000;
|
|
var localISOTime = (new Date(start.getTime() - offset)).toISOString().slice(0, 16);
|
|
document.getElementById('apt_start_time').value = localISOTime;
|
|
|
|
document.getElementById('apt_status').value = props.status;
|
|
document.getElementById('apt_reason').value = props.reason || '';
|
|
document.getElementById('btnDeleteApt').style.display = 'block';
|
|
|
|
var modalEl = document.getElementById('appointmentDetailsModal');
|
|
var modal = bootstrap.Modal.getOrCreateInstance(modalEl);
|
|
modal.show();
|
|
}
|
|
}
|
|
});
|
|
|
|
calendar.render();
|
|
|
|
doctorFilter.addEventListener('change', function() {
|
|
calendar.refetchEvents();
|
|
});
|
|
|
|
// Initialize Select2 after some delay to ensure modal is ready
|
|
$('#appointmentDetailsModal').on('shown.bs.modal', function() {
|
|
initAptSelect2();
|
|
validateHolidayFrontend();
|
|
});
|
|
|
|
// When patient changes, auto-fill address if empty and Home Visit is selected
|
|
$('#apt_patient_id').on('change', function() {
|
|
if (document.getElementById('apt_visit_type').value === 'Home') {
|
|
var addressField = document.getElementById('apt_address');
|
|
if (!addressField.value) {
|
|
var selectedOption = this.options[this.selectedIndex];
|
|
if (selectedOption && selectedOption.dataset.address) {
|
|
addressField.value = selectedOption.dataset.address;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
$('#apt_doctor_id').on('change', validateHolidayFrontend);
|
|
$('#apt_start_time').on('change', validateHolidayFrontend);
|
|
$('#apt_provider_type').on('change', validateHolidayFrontend); // Re-validate when provider changes
|
|
|
|
function validateHolidayFrontend() {
|
|
var providerType = $('#apt_provider_type').val();
|
|
if (providerType === 'Nurse') {
|
|
// Nurses don't have holiday checks yet
|
|
$('#holidayWarning').remove();
|
|
document.getElementById('btnSaveApt').disabled = false;
|
|
return;
|
|
}
|
|
|
|
var docId = $('#apt_doctor_id').val();
|
|
var startTimeStr = $('#apt_start_time').val();
|
|
var btnSave = document.getElementById('btnSaveApt');
|
|
|
|
if (!docId || !startTimeStr) return;
|
|
|
|
var datePrefix = startTimeStr.split('T')[0];
|
|
var events = calendar.getEvents();
|
|
var isHoliday = false;
|
|
|
|
for (var i = 0; i < events.length; i++) {
|
|
var ev = events[i];
|
|
// We check against the allDay visible event OR background events
|
|
if (ev.extendedProps && ev.extendedProps.type === 'doctor_holiday' && ev.extendedProps.doctor_id == docId) {
|
|
var evStartStr = ev.startStr.split('T')[0];
|
|
var evEndStr = ev.end ? ev.endStr.split('T')[0] : evStartStr;
|
|
|
|
// If it's an allDay event, the end date might be exclusive (e.g. 17th means up to 16th 23:59:59)
|
|
// If it's our new daily block background event, start and end are the same day
|
|
if (ev.allDay) {
|
|
if (datePrefix >= evStartStr && datePrefix < evEndStr) {
|
|
isHoliday = true;
|
|
break;
|
|
}
|
|
} else {
|
|
if (datePrefix === evStartStr) {
|
|
isHoliday = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isHoliday) {
|
|
if ($('#holidayWarning').length === 0) {
|
|
$('<div id="holidayWarning" class="alert alert-warning mt-3 mb-0 small"><i class="bi bi-exclamation-triangle"></i> Selected doctor is on holiday on this date.</div>').appendTo('#appointmentDetailsModal .modal-body');
|
|
}
|
|
btnSave.disabled = true;
|
|
} else {
|
|
$('#holidayWarning').remove();
|
|
btnSave.disabled = false;
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
.public-holiday-event {
|
|
background: repeating-linear-gradient(
|
|
45deg,
|
|
#dc3545,
|
|
#dc3545 10px,
|
|
#e4606d 10px,
|
|
#e4606d 20px
|
|
) !important;
|
|
border: 1px solid #c82333 !important;
|
|
opacity: 0.8;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.doctor-holiday-event {
|
|
background: repeating-linear-gradient(
|
|
45deg,
|
|
#ffc107,
|
|
#ffc107 10px,
|
|
#ffca2c 10px,
|
|
#ffca2c 20px
|
|
) !important;
|
|
border: 1px solid #e0a800 !important;
|
|
color: #856404 !important;
|
|
opacity: 0.8;
|
|
pointer-events: none; /* Make it unclickable so users can click behind it if needed */
|
|
}
|
|
|
|
/* Standard event styles now */
|
|
.doctor-holiday-bg {
|
|
border: 1px solid #e0a800 !important;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.fc-event { cursor: pointer; border: none; padding: 2px; transition: transform 0.1s ease; }
|
|
.fc-event:hover { transform: scale(1.02); z-index: 5; }
|
|
.fc-event-title { font-weight: 500; font-size: 0.85rem; }
|
|
.fc-col-header-cell { background-color: #f8f9fa; padding: 10px 0 !important; border-bottom: 2px solid #dee2e6 !important; }
|
|
.fc-day-today { background-color: rgba(0, 45, 98, 0.05) !important; }
|
|
.fc-toolbar-title { font-weight: 700; color: #002D62; font-size: 1.5rem; }
|
|
.fc .fc-button-primary { background-color: #002D62; border-color: #002D62; text-transform: capitalize; }
|
|
.fc .fc-button-primary:hover { background-color: #003a80; border-color: #003a80; }
|
|
.fc .fc-button-primary:disabled { background-color: #002D62; border-color: #002D62; opacity: 0.65; }
|
|
.fc-nonbusiness { background-color: rgba(108, 117, 125, 0.1) !important; }
|
|
.fc-v-event { box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-radius: 4px; }
|
|
|
|
.select2-container--bootstrap-5 .select2-selection { border-color: #dee2e6; background-color: #f8f9fa; }
|
|
.modal-header { border-bottom: none; }
|
|
.modal-footer { border-top: none; }
|
|
</style>
|