"; return;
}
const table = document.createElement('table');
table.className = 'ssg-schedule-table';
const thead = table.createTHead();
const tbody = table.createTBody();
const headerRow = thead.insertRow();
headerRow.insertCell().textContent = 'Employee'; // Corner cell
const dates = [];
let currentDate = new Date(ssg_schedulePeriod.startDate + 'T00:00:00'); // Ensure local time
const endDateObj = new Date(ssg_schedulePeriod.endDate + 'T00:00:00');
while (currentDate <= endDateObj) {
dates.push(new Date(currentDate)); // Store date objects
const th = headerRow.insertCell();
th.textContent = `${currentDate.getDate()}/${currentDate.getMonth() + 1}`; // DD/MM format
currentDate.setDate(currentDate.getDate() + 1);
}
ssg_teamMembers.forEach(member => {
const row = tbody.insertRow();
const nameCell = row.insertCell();
nameCell.textContent = member.name;
nameCell.className = 'employee-name-cell';
dates.forEach(dateObj => {
const cell = row.insertCell();
const dateString = dateObj.toISOString().split('T')[0];
const assignmentKey = `${member.id}_${dateString}`;
const shiftId = ssg_assignments[assignmentKey];
const shift = ssg_shiftTypes.find(s => s.id == shiftId);
if (shift) {
cell.textContent = shift.abbreviation;
cell.style.backgroundColor = shift.color;
// Make text color readable on dark backgrounds
const hexColor = shift.color.replace('#', '');
const r = parseInt(hexColor.substring(0,2), 16);
const g = parseInt(hexColor.substring(2,4), 16);
const b = parseInt(hexColor.substring(4,6), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
cell.style.color = brightness > 125 ? 'black' : 'white';
} else {
cell.textContent = '-'; // Unassigned
}
if (interactive) {
cell.classList.add('shift-cell');
cell.onclick = () => ssg_openModal(member.id, member.name, dateString);
}
});
});
container.appendChild(table);
}
// Modal Logic
function ssg_populateModalShiftSelect() {
const select = document.getElementById('ssg_modalShiftSelect');
select.innerHTML = ''; // Clear
ssg_shiftTypes.forEach(s => {
const option = document.createElement('option');
option.value = s.id;
option.textContent = `${s.name} (${s.abbreviation})`;
select.appendChild(option);
});
}
function ssg_openModal(employeeId, employeeName, dateString) {
document.getElementById('ssg_modalEmployeeId').value = employeeId;
document.getElementById('ssg_modalEmployeeName').textContent = employeeName;
document.getElementById('ssg_modalDate').textContent = dateString;
document.getElementById('ssg_modalFullDateString').value = dateString;
const assignmentKey = `${employeeId}_${dateString}`;
const currentShiftId = ssg_assignments[assignmentKey];
document.getElementById('ssg_modalShiftSelect').value = currentShiftId || ssg_shiftTypes.find(s => s.isDefault)?.id || ''; // Default to Day Off or first if not assigned
document.getElementById('ssg_shiftAssignmentModal').style.display = 'block';
}
function ssg_closeModal() {
document.getElementById('ssg_shiftAssignmentModal').style.display = 'none';
}
function ssg_assignShiftFromModal() {
const employeeId = document.getElementById('ssg_modalEmployeeId').value;
const dateString = document.getElementById('ssg_modalFullDateString').value;
const shiftId = document.getElementById('ssg_modalShiftSelect').value;
const assignmentKey = `${employeeId}_${dateString}`;
if (shiftId) {
ssg_assignments[assignmentKey] = shiftId;
} else { // Should not happen if select is populated
delete ssg_assignments[assignmentKey];
}
ssg_closeModal();
ssg_renderScheduleGrid('ssg_scheduleGridContainer', true); // Re-render interactive grid
}
// PDF Download
function ssg_downloadPDF() {
if (typeof jsPDF === 'undefined' || typeof jsPDF.API === 'undefined' || typeof jsPDF.API.autoTable === 'undefined') {
alert("PDF generation library is not loaded."); console.error("jsPDF or jsPDF-AutoTable is not loaded."); return;
}
ssg_updateSchedulePeriod(); // Ensure period is current
if (!ssg_schedulePeriod.startDate || !ssg_schedulePeriod.endDate || new Date(ssg_schedulePeriod.endDate) < new Date(ssg_schedulePeriod.startDate)) {
alert("Please set a valid schedule period."); return;
}
const doc = new jsPDF({ orientation: 'landscape' }); // Landscape often better for schedules
const primaryColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim();
const textColorPDF = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim();
const whiteColorPDF = '#FFFFFF';
doc.setFontSize(18);
doc.setTextColor(primaryColorPDF);
doc.text("Team Shift Schedule", 14, 15);
doc.setFontSize(10);
doc.setTextColor(textColorPDF);
doc.text(`Period: ${ssg_schedulePeriod.startDate} to ${ssg_schedulePeriod.endDate}`, 14, 22);
doc.text(`Generated On: ${SSG_CONTEXT_DATE}`, 14, 27);
let currentY = 35;
// Shift Legend
doc.setFontSize(12);
doc.setTextColor(primaryColorPDF);
doc.text("Shift Legend:", 14, currentY);
currentY += 6;
const legendCols = ["Abbr.", "Shift Name", "Time"];
const legendRows = ssg_shiftTypes.map(s => [
s.abbreviation, s.name, (s.startTime && s.endTime) ? `${s.startTime}-${s.endTime}` : '-'
]);
doc.autoTable({
head: [legendCols], body: legendRows, startY: currentY,
theme: 'grid', headStyles: { fillColor: primaryColorPDF, textColor: whiteColorPDF, fontSize: 8 },
styles: { fontSize: 7, cellPadding: 1.5, textColor: textColorPDF },
columnStyles: { 0: { cellWidth: 15 }, 1: { cellWidth: 'auto'}, 2: {cellWidth: 30} }
});
currentY = doc.lastAutoTable.finalY + 8;
// Schedule Table
doc.setFontSize(12);
doc.setTextColor(primaryColorPDF);
doc.text("Schedule:", 14, currentY);
currentY += 6;
const datesHeader = ['Employee'];
const datesForGrid = [];
let tempDate = new Date(ssg_schedulePeriod.startDate + 'T00:00:00');
const endDateObj = new Date(ssg_schedulePeriod.endDate + 'T00:00:00');
while(tempDate <= endDateObj) {
datesHeader.push(`${tempDate.getDate()}/${tempDate.getMonth()+1}`);
datesForGrid.push(new Date(tempDate));
tempDate.setDate(tempDate.getDate() + 1);
}
const scheduleTableRows = ssg_teamMembers.map(member => {
const row = [member.name];
datesForGrid.forEach(dateObj => {
const dateString = dateObj.toISOString().split('T')[0];
const assignmentKey = `${member.id}_${dateString}`;
const shiftId = ssg_assignments[assignmentKey];
const shift = ssg_shiftTypes.find(s => s.id == shiftId);
row.push(shift ? shift.abbreviation : '-');
});
return row;
});
doc.autoTable({
head: [datesHeader], body: scheduleTableRows, startY: currentY,
theme: 'grid',
headStyles: { fillColor: primaryColorPDF, textColor: whiteColorPDF, fontSize: 7, halign: 'center' },
styles: { fontSize: 7, cellPadding: 1, textColor: textColorPDF, halign: 'center' },
didDrawCell: function(data) { // For cell coloring
if (data.section === 'body' && data.column.index > 0) { // Not employee name column
const member = ssg_teamMembers[data.row.index];
const dateObj = datesForGrid[data.column.index - 1];
const dateString = dateObj.toISOString().split('T')[0];
const assignmentKey = `${member.id}_${dateString}`;
const shiftId = ssg_assignments[assignmentKey];
const shift = ssg_shiftTypes.find(s => s.id == shiftId);
if (shift && shift.color) {
doc.setFillColor(shift.color);
doc.rect(data.cell.x, data.cell.y, data.cell.width, data.cell.height, 'F');
// Adjust text color for readability on cell background
const hexColor = shift.color.replace('#', '');
const r = parseInt(hexColor.substring(0,2), 16);
const g = parseInt(hexColor.substring(2,4), 16);
const b = parseInt(hexColor.substring(4,6), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
const cellTextColor = brightness > 125 ? [0,0,0] : [255,255,255];
doc.setTextColor(cellTextColor[0], cellTextColor[1], cellTextColor[2]);
// Note: autoTable might redraw text, so this text color change might be tricky.
// It's often better to handle text color in `addPageContent` or prepare data with styles.
// For simplicity here, default text color is used by autoTable after fill.
// A more robust solution involves deeper customization of jsPDF-autoTable drawing.
}
}
},
// Make employee name column wider and stick if possible (not native to autotable)
columnStyles: { 0: { fontStyle: 'bold', cellWidth: 40, halign: 'left' } }
});
doc.save(`Team_Shift_Schedule_${ssg_schedulePeriod.startDate}_to_${ssg_schedulePeriod.endDate}.pdf`);
}