Roster
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Student Shift Roster</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
color: #333;
padding: 20px;
min-height: 100vh;
}
.container {
max-width: 1400px;
margin: 20px auto;
background: rgba(255, 255, 255, 0.95);
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
header {
background: linear-gradient(to right, #2c3e50, #1a2a6c);
color: white;
padding: 25px;
text-align: center;
position: relative;
}
header h1 {
font-size: 2.5rem;
margin-bottom: 10px;
font-weight: 700;
}
.subtitle {
font-size: 1.2rem;
opacity: 0.9;
max-width: 700px;
margin: 0 auto;
}
.controls {
display: flex;
justify-content: space-between;
padding: 20px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
flex-wrap: wrap;
gap: 15px;
}
.week-navigation {
display: flex;
align-items: center;
gap: 10px;
}
.week-navigation button {
background: #3498db;
color: white;
border: none;
padding: 10px 20px;
border-radius: 30px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.week-navigation button:hover {
background: #2980b9;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.current-week {
font-weight: 700;
font-size: 1.2rem;
background: #2c3e50;
color: white;
padding: 8px 20px;
border-radius: 30px;
min-width: 300px;
}
.action-buttons {
display: flex;
gap: 10px;
}
.action-buttons button {
background: #27ae60;
color: white;
border: none;
padding: 10px 20px;
border-radius: 30px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.action-buttons button:hover {
background: #219653;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.action-buttons button.add-leave {
background: #e74c3c;
}
.action-buttons button.add-leave:hover {
background: #c0392b;
}
.shift-legend {
display: flex;
justify-content: center;
gap: 10px;
margin: 20px 0;
padding: 0 20px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 15px;
border-radius: 30px;
font-size: 0.9rem;
font-weight: 500;
background: #f0f0f0;
}
.legend-color {
width: 20px;
height: 20px;
border-radius: 50%;
}
.roster-container {
overflow-x: auto;
padding: 20px;
}
.roster-table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.08);
}
.roster-table th {
background-color: #2c3e50;
color: white;
padding: 15px;
text-align: center;
position: sticky;
top: 0;
z-index: 10;
}
.roster-table th.day-header {
background: #34495e;
}
.roster-table td {
padding: 15px;
text-align: center;
border: 1px solid #e0e0e0;
position: relative;
}
.date-display {
font-size: 0.9rem;
font-weight: 600;
margin-top: 5px;
color: #f1c40f;
}
.student-name-cell {
font-weight: 700;
background-color: #f8f9fa;
position: sticky;
left: 0;
z-index: 5;
min-width: 180px;
text-align: left;
padding-left: 20px;
}
.student-name-cell .contact {
font-size: 0.85rem;
color: #7f8c8d;
font-weight: 400;
margin-top: 5px;
}
.student-name-cell .remove-btn {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: transparent;
border: none;
color: #e74c3c;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s;
}
.student-name-cell:hover .remove-btn {
opacity: 1;
}
.shift-cell {
font-weight: 600;
cursor: pointer;
min-width: 120px;
height: 80px;
transition: all 0.2s;
position: relative;
}
.shift-cell:hover {
transform: scale(1.03);
z-index: 2;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
}
.shift-10-7 { background-color: #e74c3c; color: white; }
.shift-9-6 { background-color: #f39c12; color: white; }
.shift-7-4 { background-color: #3498db; color: white; }
.shift-1-10 { background-color: #2ecc71; color: white; }
.off-day {
background: linear-gradient(135deg, #bdc3c7, #95a5a6);
color: #2c3e50;
font-style: italic;
font-weight: 600;
}
.leave-day {
background: linear-gradient(135deg, #9b59b6, #8e44ad);
color: white;
font-weight: 600;
}
.long-leave {
background: repeating-linear-gradient(
45deg,
#9b59b6,
#9b59b6 10px,
#8e44ad 10px,
#8e44ad 20px
);
color: white;
font-weight: 600;
}
.shift-info {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10px;
opacity: 0;
transition: opacity 0.3s;
background: rgba(0, 0, 0, 0.7);
color: white;
font-size: 0.9rem;
text-align: center;
}
.shift-cell:hover .shift-info {
opacity: 1;
}
.student-form {
background-color: #f8f9fa;
padding: 25px;
border-radius: 10px;
margin: 20px;
}
.form-title {
text-align: center;
margin-bottom: 20px;
color: #2c3e50;
font-size: 1.5rem;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.form-group {
flex: 1;
min-width: 250px;
}
label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2c3e50;
}
input, select {
width: 100%;
padding: 12px 15px;
border: 1px solid #ddd;
border-radius: 8px;
font-size: 1rem;
transition: border 0.3s;
}
input:focus, select:focus {
outline: none;
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.2);
}
.form-actions {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.form-actions button {
padding: 12px 25px;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
font-size: 1rem;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 8px;
}
.form-actions button.add-btn {
background: #27ae60;
color: white;
}
.form-actions button.add-btn:hover {
background: #219653;
transform: translateY(-3px);
}
.form-actions button.cancel-btn {
background: #e74c3c;
color: white;
}
.form-actions button.cancel-btn:hover {
background: #c0392b;
transform: translateY(-3px);
}
.footer-note {
margin: 20px;
padding: 20px;
background: #e8f4fd;
border-radius: 10px;
border-left: 5px solid #3498db;
font-size: 0.95rem;
}
.footer-note h3 {
margin-bottom: 10px;
color: #2c3e50;
}
.stats {
display: flex;
justify-content: space-around;
margin: 20px 0;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
}
.stat-card {
text-align: center;
padding: 15px;
border-radius: 10px;
background: white;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
min-width: 150px;
}
.stat-value {
font-size: 2rem;
font-weight: 700;
color: #3498db;
}
.stat-label {
font-size: 0.9rem;
color: #7f8c8d;
}
.empty-roster {
text-align: center;
padding: 50px;
color: #7f8c8d;
font-size: 1.2rem;
}
.empty-roster i {
font-size: 3rem;
margin-bottom: 20px;
color: #bdc3c7;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
border-radius: 8px;
background: #2ecc71;
color: white;
font-weight: 600;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateX(120%);
transition: transform 0.3s ease-in-out;
z-index: 1000;
}
.notification.show {
transform: translateX(0);
}
.notification.error {
background: #e74c3c;
}
@media (max-width: 768px) {
.controls {
flex-direction: column;
align-items: center;
}
.action-buttons {
width: 100%;
justify-content: center;
}
.roster-table {
font-size: 0.85rem;
}
.roster-table th,
.roster-table td {
padding: 10px 8px;
}
.shift-cell {
height: 70px;
}
.stats {
flex-direction: column;
gap: 10px;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><i class="fas fa-calendar-alt"></i> Student Shift Roster</h1>
<p class="subtitle">Automated shift scheduling with leave management for students</p>
</header>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="student-count">0</div>
<div class="stat-label">Students</div>
</div>
<div class="stat-card">
<div class="stat-value">4</div>
<div class="stat-label">Shift Types</div>
</div>
<div class="stat-card">
<div class="stat-value" id="shift-count">0</div>
<div class="stat-label">Shifts This Week</div>
</div>
<div class="stat-card">
<div class="stat-value" id="leave-count">0</div>
<div class="stat-label">Leaves</div>
</div>
</div>
<div class="controls">
<div class="week-navigation">
<button id="prev-week"><i class="fas fa-chevron-left"></i> Previous Week</button>
<div class="current-week" id="current-week">Week of June 10, 2024 - June 16, 2024</div>
<button id="next-week">Next Week <i class="fas fa-chevron-right"></i></button>
</div>
<div class="action-buttons">
<button id="add-leave"><i class="fas fa-plus-circle"></i> Add Sudden Leave</button>
<button id="add-long-leave" class="add-leave"><i class="fas fa-calendar-times"></i> Add Extended Leave</button>
</div>
</div>
<div class="shift-legend">
<div class="legend-item"><div class="legend-color" style="background-color: #e74c3c"></div> 10:30 AM - 7:30 PM</div>
<div class="legend-item"><div class="legend-color" style="background-color: #f39c12"></div> 9:30 AM - 6:30 PM</div>
<div class="legend-item"><div class="legend-color" style="background-color: #3498db"></div> 7:30 AM - 4:30 PM</div>
<div class="legend-item"><div class="legend-color" style="background-color: #2ecc71"></div> 1:30 PM - 10:30 PM</div>
<div class="legend-item"><div class="legend-color" style="background: linear-gradient(135deg, #bdc3c7, #95a5a6)"></div> Week Off</div>
<div class="legend-item"><div class="legend-color" style="background: linear-gradient(135deg, #9b59b6, #8e44ad)"></div> Sudden Leave</div>
<div class="legend-item"><div class="legend-color" style="background: repeating-linear-gradient(45deg, #9b59b6, #9b59b6 10px, #8e44ad 10px, #8e44ad 20px)"></div> Extended Leave</div>
</div>
<div class="roster-container">
<table class="roster-table">
<thead>
<tr>
<th>Student / Contact</th>
<!-- Days will be populated by JavaScript -->
</tr>
</thead>
<tbody id="roster-body">
<!-- Roster will be generated here -->
</tbody>
</table>
</div>
<div class="student-form">
<h2 class="form-title"><i class="fas fa-user-graduate"></i> Add New Student</h2>
<div class="form-row">
<div class="form-group">
<label for="student-name"><i class="fas fa-user"></i> Student Name</label>
<input type="text" id="student-name" placeholder="Enter student name">
</div>
<div class="form-group">
<label for="student-contact"><i class="fas fa-phone"></i> Contact (Optional)</label>
<input type="text" id="student-contact" placeholder="Phone or email">
</div>
<div class="form-group">
<label for="shift-type"><i class="fas fa-clock"></i> Shift Type</label>
<select id="shift-type">
<option value="shift-7-4">7:30 AM - 4:30 PM</option>
<option value="shift-9-6">9:30 AM - 6:30 PM</option>
<option value="shift-10-7">10:30 AM - 7:30 PM</option>
<option value="shift-1-10">1:30 PM - 10:30 PM</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="week-off-day"><i class="fas fa-calendar-day"></i> Week Off Day</label>
<select id="week-off-day">
<option value="0">Monday</option>
<option value="1">Tuesday</option>
<option value="2">Wednesday</option>
<option value="3">Thursday</option>
<option value="4">Friday</option>
<option value="5">Saturday</option>
<option value="6">Sunday</option>
</select>
</div>
</div>
<div class="form-actions">
<button class="add-btn" id="add-student"><i class="fas fa-user-plus"></i> Add Student</button>
<button class="cancel-btn" id="reset-form"><i class="fas fa-times"></i> Clear Form</button>
</div>
</div>
<div class="footer-note">
<h3><i class="fas fa-info-circle"></i> Roster Information</h3>
<p>• This roster automatically rotates shifts at the end of each week. Each student gets one week off per week (mostly on Sunday).</p>
<p>• At least 4 students are scheduled for each shift every day to ensure proper coverage.</p>
<p>• Hover over any shift to see detailed information about the student and schedule.</p>
<p>• Extended leaves can be added for up to 4 weeks. During leave periods, shifts are automatically reassigned.</p>
</div>
</div>
<div class="notification" id="notification">Student added successfully!</div>
<script>
// Start with an empty roster
let students = [];
let suddenLeaves = [];
let extendedLeaves = [];
// Current week tracking
let currentWeekStart = new Date();
currentWeekStart.setDate(currentWeekStart.getDate() - currentWeekStart.getDay() + 1);
currentWeekStart.setHours(0, 0, 0, 0);
// DOM Elements
const rosterBody = document.getElementById('roster-body');
const rosterHead = document.querySelector('.roster-table thead');
const currentWeekEl = document.getElementById('current-week');
const prevWeekBtn = document.getElementById('prev-week');
const nextWeekBtn = document.getElementById('next-week');
const addLeaveBtn = document.getElementById('add-leave');
const addLongLeaveBtn = document.getElementById('add-long-leave');
const addStudentBtn = document.getElementById('add-student');
const resetFormBtn = document.getElementById('reset-form');
const studentCountEl = document.getElementById('student-count');
const shiftCountEl = document.getElementById('shift-count');
const leaveCountEl = document.getElementById('leave-count');
const notification = document.getElementById('notification');
// Initialize the roster
function initRoster() {
updateWeekDisplay();
renderRoster();
updateStats();
}
// Update the current week display
function updateWeekDisplay() {
const startDate = new Date(currentWeekStart);
const endDate = new Date(currentWeekStart);
endDate.setDate(endDate.getDate() + 6);
const options = { month: 'long', day: 'numeric', year: 'numeric' };
currentWeekEl.textContent = `Week of ${startDate.toLocaleDateString('en-US', options)} - ${endDate.toLocaleDateString('en-US', options)}`;
}
// Show notification
function showNotification(message, isError = false) {
notification.textContent = message;
notification.className = isError ? 'notification error' : 'notification';
notification.classList.add('show');
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// Render the roster table
function renderRoster() {
// Clear existing content
rosterHead.innerHTML = '<tr><th>Student / Contact</th></tr>';
rosterBody.innerHTML = '';
if (students.length === 0) {
rosterBody.innerHTML = `
<tr>
<td colspan="8" class="empty-roster">
<i class="fas fa-user-graduate"></i>
<h3>No Students Added</h3>
<p>Add students using the form below to start creating your roster</p>
</td>
</tr>
`;
return;
}
// Create header row with dates
const headerRow = document.createElement('tr');
const studentHeader = document.createElement('th');
studentHeader.textContent = 'Student / Contact';
headerRow.appendChild(studentHeader);
// Add day headers with dates
for (let i = 0; i < 7; i++) {
const date = new Date(currentWeekStart);
date.setDate(date.getDate() + i);
const dayHeader = document.createElement('th');
dayHeader.className = 'day-header';
const dayName = date.toLocaleDateString('en-US', { weekday: 'short' });
const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
dayHeader.innerHTML = `${dayName}<div class="date-display">${dateStr}</div>`;
headerRow.appendChild(dayHeader);
}
rosterHead.appendChild(headerRow);
// Add student rows
students.forEach(student => {
const row = document.createElement('tr');
// Student name cell
const nameCell = document.createElement('td');
nameCell.className = 'student-name-cell';
nameCell.innerHTML = `
<div>
${student.name}
<button class="remove-btn" data-id="${student.id}" title="Remove student">
<i class="fas fa-trash-alt"></i>
</button>
</div>
<div class="contact">${student.contact || 'No contact'}</div>
`;
row.appendChild(nameCell);
// Create cells for each day of the week
for (let day = 0; day < 7; day++) {
const cell = document.createElement('td');
const date = new Date(currentWeekStart);
date.setDate(date.getDate() + day);
const dateStr = date.toISOString().split('T')[0];
// Check for extended leave
const onExtendedLeave = isOnExtendedLeave(student.id, dateStr);
if (onExtendedLeave) {
cell.className = 'shift-cell long-leave';
cell.innerHTML = `
<div>Extended Leave</div>
<div class="shift-info">
${student.name}<br>
${getLeaveReason(student.id, dateStr)}<br>
${formatDateRange(onExtendedLeave.start, onExtendedLeave.end)}
</div>
`;
} else if (day === student.weekOff) {
// Week off day
cell.className = 'shift-cell off-day';
cell.innerHTML = `
<div>Week Off</div>
<div class="shift-info">
${student.name}<br>
Weekly day off<br>
${getShiftTime(student.shift)} shift on other days
</div>
`;
} else if (isOnSuddenLeave(student.id, day, dateStr)) {
// Sudden leave
cell.className = 'shift-cell leave-day';
cell.innerHTML = `
<div>Sudden Leave</div>
<div class="shift-info">
${student.name}<br>
${getLeaveReason(student.id, dateStr) || 'Emergency leave'}<br>
Normally: ${getShiftTime(student.shift)}
</div>
`;
} else {
// Regular shift
cell.className = `shift-cell ${student.shift}`;
cell.innerHTML = `
<div>${getShiftTime(student.shift)}</div>
<div class="shift-info">
${student.name}<br>
${student.contact ? `Contact: ${student.contact}` : ''}<br>
Week Off: ${getDayName(student.weekOff)}
</div>
`;
}
row.appendChild(cell);
}
rosterBody.appendChild(row);
});
// Add event listeners to remove buttons
document.querySelectorAll('.remove-btn').forEach(button => {
button.addEventListener('click', function() {
const studentId = this.getAttribute('data-id');
removeStudent(studentId);
});
});
updateStats();
}
// Remove a student from the roster
function removeStudent(studentId) {
if (confirm('Are you sure you want to remove this student from the roster?')) {
const studentIndex = students.findIndex(s => s.id == studentId);
if (studentIndex !== -1) {
const studentName = students[studentIndex].name;
students.splice(studentIndex, 1);
// Remove any leaves associated with this student
suddenLeaves = suddenLeaves.filter(leave => leave.studentId != studentId);
extendedLeaves = extendedLeaves.filter(leave => leave.studentId != studentId);
renderRoster();
showNotification(`${studentName} has been removed from the roster`);
}
}
}
// Update statistics
function updateStats() {
studentCountEl.textContent = students.length;
// Calculate shift count (total shifts minus week offs and leaves)
let shiftCount = 0;
if (students.length > 0) {
shiftCount = students.length * 7; // 7 days per student
// Subtract week offs
students.forEach(student => {
shiftCount--; // for the week off
});
// Subtract sudden leaves
shiftCount -= suddenLeaves.length;
// Subtract extended leaves
extendedLeaves.forEach(leave => {
const start = new Date(leave.start);
const end = new Date(leave.end);
const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
shiftCount -= days;
});
}
shiftCountEl.textContent = shiftCount;
leaveCountEl.textContent = suddenLeaves.length + extendedLeaves.length;
}
// Get formatted shift time
function getShiftTime(shiftType) {
switch(shiftType) {
case 'shift-7-4': return '7:30-4:30';
case 'shift-9-6': return '9:30-6:30';
case 'shift-10-7': return '10:30-7:30';
case 'shift-1-10': return '1:30-10:30';
default: return '';
}
}
// Get day name from index
function getDayName(dayIndex) {
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
return days[dayIndex];
}
// Format date range
function formatDateRange(start, end) {
const startDate = new Date(start);
const endDate = new Date(end);
return `${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()}`;
}
// Check if student is on sudden leave
function isOnSuddenLeave(studentId, day, dateStr) {
return suddenLeaves.some(leave =>
leave.studentId === studentId &&
(leave.day === day || leave.date === dateStr)
);
}
// Check if student is on extended leave
function isOnExtendedLeave(studentId, dateStr) {
const date = new Date(dateStr);
return extendedLeaves.find(leave =>
leave.studentId === studentId &&
new Date(leave.start) <= date &&
new Date(leave.end) >= date
);
}
// Get leave reason
function getLeaveReason(studentId, dateStr) {
const suddenLeave = suddenLeaves.find(leave =>
leave.studentId === studentId &&
(leave.date === dateStr || leave.day === new Date(dateStr).getDay())
);
if (suddenLeave) return suddenLeave.reason;
const extendedLeave = extendedLeaves.find(leave =>
leave.studentId === studentId &&
new Date(leave.start) <= new Date(dateStr) &&
new Date(leave.end) >= new Date(dateStr)
);
if (extendedLeave) return extendedLeave.reason;
return null;
}
// Add a new student
function addStudent() {
const nameInput = document.getElementById('student-name');
const contactInput = document.getElementById('student-contact');
const shiftSelect = document.getElementById('shift-type');
const weekOffSelect = document.getElementById('week-off-day');
if (!nameInput.value.trim()) {
showNotification('Please enter a student name', true);
return;
}
const newStudent = {
id: Date.now(), // Use timestamp for unique ID
name: nameInput.value.trim(),
contact: contactInput.value.trim(),
shift: shiftSelect.value,
weekOff: parseInt(weekOffSelect.value)
};
students.push(newStudent);
// Clear form
nameInput.value = '';
contactInput.value = '';
shiftSelect.value = 'shift-7-4';
weekOffSelect.value = '6';
renderRoster();
showNotification(`${newStudent.name} added to the roster`);
}
// Add a sudden leave
function addSuddenLeave() {
if (students.length === 0) {
showNotification('No students in roster', true);
return;
}
const studentName = prompt("Enter student name for leave:");
if (!studentName) return;
const student = students.find(s => s.name === studentName);
if (!student) {
showNotification('Student not found', true);
return;
}
const leaveDay = prompt("Enter day for leave (Mon, Tue, etc):");
if (!leaveDay) return;
const dayIndex = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun']
.indexOf(leaveDay.toLowerCase().substring(0, 3));
if (dayIndex === -1) {
showNotification('Invalid day entered', true);
return;
}
// Can't add leave on week off day
if (dayIndex === student.weekOff) {
showNotification('Cannot add leave on student\'s week off day', true);
return;
}
const reason = prompt("Enter reason for leave:") || "Emergency leave";
suddenLeaves.push({
studentId: student.id,
day: dayIndex,
reason: reason
});
renderRoster();
showNotification(`Added sudden leave for ${student.name} on ${getDayName(dayIndex)}: ${reason}`);
}
// Add an extended leave
function addExtendedLeave() {
if (students.length === 0) {
showNotification('No students in roster', true);
return;
}
const studentName = prompt("Enter student name for extended leave:");
if (!studentName) return;
const student = students.find(s => s.name === studentName);
if (!student) {
showNotification('Student not found', true);
return;
}
const startDate = prompt("Enter start date (YYYY-MM-DD):");
if (!startDate || !isValidDate(startDate)) {
showNotification('Invalid start date', true);
return;
}
const endDate = prompt("Enter end date (YYYY-MM-DD):");
if (!endDate || !isValidDate(endDate)) {
showNotification('Invalid end date', true);
return;
}
if (new Date(endDate) < new Date(startDate)) {
showNotification('End date must be after start date', true);
return;
}
const reason = prompt("Enter reason for leave:") || "Extended leave";
extendedLeaves.push({
studentId: student.id,
start: startDate,
end: endDate,
reason: reason
});
renderRoster();
showNotification(`Added extended leave for ${student.name} from ${startDate} to ${endDate}: ${reason}`);
}
// Validate date format
function isValidDate(dateString) {
const regEx = /^\d{4}-\d{2}-\d{2}$/;
if (!dateString.match(regEx)) return false;
const d = new Date(dateString);
return d instanceof Date && !isNaN(d);
}
// Rotate shifts for the next week
function rotateShifts() {
students.forEach(student => {
// Sunday off students keep Sunday off
if (student.weekOff === 6) return;
// Rotate week off day to next day (with wrap around)
student.weekOff = (student.weekOff + 1) % 7;
});
}
// Event Listeners
prevWeekBtn.addEventListener('click', () => {
currentWeekStart.setDate(currentWeekStart.getDate() - 7);
updateWeekDisplay();
renderRoster();
});
nextWeekBtn.addEventListener('click', () => {
currentWeekStart.setDate(currentWeekStart.getDate() + 7);
// If we're moving to a new week, rotate shifts
if (currentWeekStart > new Date()) {
rotateShifts();
}
updateWeekDisplay();
renderRoster();
});
addLeaveBtn.addEventListener('click', addSuddenLeave);
addLongLeaveBtn.addEventListener('click', addExtendedLeave);
addStudentBtn.addEventListener('click', addStudent);
resetFormBtn.addEventListener('click', () => {
document.getElementById('student-name').value = '';
document.getElementById('student-contact').value = '';
document.getElementById('shift-type').value = 'shift-7-4';
document.getElementById('week-off-day').value = '6';
});
// Initialize the roster
initRoster();
</script>
</body>
</html>
Comments
Post a Comment