// Global teacher config arrays (populated via AJAX).
let teacherClass = "";
let subjectList = [];
let specSubjectList = [];
let batchList = [];
let practicalSubjectList = [];
// Timetable parameters.
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
const timeSlots = [
'8.45 - 9.40', // index 0
'9.40 - 10.35', // index 1
'10.35 - 10.50', // index 2 -> Short Break (fixed)
'10.50 - 11.45', // index 3
'11.45 - 12.40', // index 4
'12.40 - 1.40', // index 5 -> Lunch Break (fixed)
'1.40 - 2.35', // index 6
'2.35 - 3.30', // index 7
'3.30 - 3.40', // index 8 -> Short Break (fixed)
'3.40 - 4.30' // index 9
];
// Mapping break indices to fixed texts.
const breakSlots = {
2: "Short Break",
5: "Lunch Break",
8: "Short Break"
};
// Two-hour practical time slot definitions.
const practicalTimeSlots = [
{ label: '8.45 - 10.35', indices: [0, 1] },
{ label: '10.50 - 12.40', indices: [3, 4] },
{ label: '1.40 - 3.30', indices: [6, 7] }
];
let timetableData = {}; // Keys: "row-col-day"
let mergedCells = {}; // Keys: "row-col" mapped to an array of merged cell keys
// ------ Teacher Config Functions (AJAX calls) ------
function loadTeacherConfig() {
if (!teacherClass) return;
getConfig("subject");
getConfig("specSubject");
getConfig("batch");
getConfig("practical");
}
function getConfig(type) {
fetch(`/get_config?class=${teacherClass}&type=${type}`)
.then(res => res.json())
.then(data => {
if (type === "subject") {
subjectList = data;
updateDisplay("subjectList", subjectList, "subject");
} else if (type === "specSubject") {
specSubjectList = data;
updateDisplay("specSubjectList", specSubjectList, "specSubject");
} else if (type === "batch") {
batchList = data;
updateDisplay("batchList", batchList);
} else if (type === "practical") {
practicalSubjectList = data;
updateDisplay("practicalSubjectList", practicalSubjectList);
}
});
}
function addConfig(type) {
if (!teacherClass) {
alert("Please select a class first.");
return;
}
let inputId = "";
if (type === "subject") inputId = "subjectInput";
else if (type === "specSubject") inputId = "specSubjectInput";
else if (type === "batch") inputId = "batchInput";
else if (type === "practical") inputId = "practicalSubjectInput";
const value = document.getElementById(inputId).value.trim();
if (!value) return;
fetch('/add_config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({class: teacherClass, type: type, name: value})
})
.then(res => res.json())
.then(data => {
loadTeacherConfig();
document.getElementById(inputId).value = "";
});
}
function updateDisplay(elementId, arr, type="") {
const span = document.getElementById(elementId);
span.innerHTML = "";
arr.forEach(item => {
const spanItem = document.createElement("span");
spanItem.textContent = item;
const del = document.createElement("span");
del.textContent = " ✕";
del.className = "delete-btn";
del.onclick = () => deleteConfig(type, item);
spanItem.appendChild(del);
span.appendChild(spanItem);
span.appendChild(document.createTextNode(" | "));
});
}
function deleteConfig(type, name) {
if (!teacherClass) return;
fetch('/delete_config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({class: teacherClass, type: type, name: name})
})
.then(res => res.json())
.then(data => loadTeacherConfig());
}
// When a class is selected, load both configuration and timetable.
function loadForSelectedClass() {
teacherClass = document.getElementById("classSelect").value;
if (!teacherClass) return;
loadTeacherConfig();
loadTimetable();
}
// ------ Modal & Dropdown Functions ------
function openModal() {
if (!teacherClass) {
alert("Please select a class first.");
return;
}
populateDropdown("subjectSelect", subjectList);
populateDropdown("specSubjectSelect", specSubjectList);
populateDropdown("practicalSelect", practicalSubjectList);
populateDropdown("batchSelect", batchList);
document.getElementById("timeSlotSelect").innerHTML = "";
document.getElementById("optionType").value = "";
hideAllModalOptions();
document.getElementById("popupModal").style.display = "flex";
}
function closeModal() {
document.getElementById("popupModal").style.display = "none";
}
function hideAllModalOptions() {
document.getElementById("subjectDropdown").style.display = "none";
document.getElementById("specSubjectDropdown").style.display = "none";
document.getElementById("practicalDropdown").style.display = "none";
document.getElementById("timeSlotDiv").style.display = "none";
document.getElementById("submitBtn").style.display = "none";
}
function populateDropdown(dropdownId, items) {
const dropdown = document.getElementById(dropdownId);
dropdown.innerHTML = '';
items.forEach(item => {
const opt = document.createElement("option");
opt.value = item;
opt.textContent = item;
dropdown.appendChild(opt);
});
}
function optionTypeChanged() {
hideAllModalOptions();
const optionType = document.getElementById("optionType").value;
if (optionType === "subject") {
document.getElementById("subjectDropdown").style.display = "block";
populateTimeSlotsDropdown("one-hour");
} else if (optionType === "specSubject") {
document.getElementById("specSubjectDropdown").style.display = "block";
populateTimeSlotsDropdown("one-hour");
} else if (optionType === "practical") {
document.getElementById("practicalDropdown").style.display = "block";
populateTimeSlotsDropdown("two-hour");
}
}
function populateTimeSlotsDropdown(type) {
const timeSlotSelect = document.getElementById("timeSlotSelect");
timeSlotSelect.innerHTML = '';
if (type === "one-hour") {
timeSlots.forEach((slot, idx) => {
if (breakSlots.hasOwnProperty(idx)) return;
const opt = document.createElement("option");
opt.value = idx;
opt.textContent = slot;
timeSlotSelect.appendChild(opt);
});
} else if (type === "two-hour") {
practicalTimeSlots.forEach((slot, idx) => {
const opt = document.createElement("option");
opt.value = idx;
opt.textContent = slot.label;
timeSlotSelect.appendChild(opt);
});
}
document.getElementById("timeSlotDiv").style.display = "block";
document.getElementById("submitBtn").style.display = "block";
}
// ------ Timetable Functions ------
function inferMergedCells() {
mergedCells = {};
const mergeStarts = [0, 3, 6]; // indices where merges can start
days.forEach((day, colIdx) => {
mergeStarts.forEach(startIdx => {
const mainKey = startIdx + "-" + colIdx + "-" + day;
const nextKey = (startIdx + 1) + "-" + colIdx + "-" + day;
if (timetableData[mainKey] && !timetableData[nextKey]) {
const cellKey = startIdx + "-" + colIdx;
const nextCellKey = (startIdx + 1) + "-" + colIdx;
mergedCells[cellKey] = [cellKey, nextCellKey];
}
});
});
}
function generateTable() {
const table = document.getElementById("timetable");
table.innerHTML = "";
const header = table.insertRow();
header.insertCell().textContent = "Time";
days.forEach(day => header.insertCell().textContent = day);
timeSlots.forEach((slot, rowIdx) => {
const row = table.insertRow();
row.insertCell().textContent = slot;
days.forEach((day, colIdx) => {
const cellKey = rowIdx + "-" + colIdx;
if (breakSlots.hasOwnProperty(rowIdx)) {
const cell = row.insertCell();
cell.innerHTML = breakSlots[rowIdx];
cell.style.fontWeight = "bold";
cell.style.backgroundColor = "#f0f0f0";
} else if (isMergedCell(cellKey)) {
return;
} else {
const cell = row.insertCell();
const contentKey = cellKey + "-" + day;
let content = timetableData[contentKey] || "";
if (mergedCells[cellKey]) {
cell.rowSpan = mergedCells[cellKey].length;
}
if (content.includes("
")) {
const items = content.split("
");
cell.innerHTML = "";
items.forEach((batch, i) => {
const div = document.createElement("div");
div.innerHTML = batch;
const del = document.createElement("span");
del.textContent = " [✕]";
del.className = "delete-btn";
del.onclick = () => {
const filtered = items.filter(it => it !== batch);
timetableData[contentKey] = filtered.join("
");
if (!timetableData[contentKey]) delete timetableData[contentKey];
autoSaveTimetable();
generateTable();
};
div.appendChild(del);
cell.appendChild(div);
});
const clearAll = document.createElement("button");
clearAll.textContent = "Clear all";
clearAll.onclick = () => {
delete timetableData[contentKey];
autoSaveTimetable();
generateTable();
};
cell.appendChild(clearAll);
} else {
cell.innerHTML = content;
if (content) {
const del = document.createElement("span");
del.textContent = " [✕]";
del.className = "delete-btn";
del.onclick = () => {
delete timetableData[contentKey];
autoSaveTimetable();
generateTable();
};
cell.appendChild(del);
}
}
}
});
});
}
function isMergedCell(cellKey) {
for (const mainKey in mergedCells) {
if (mergedCells[mainKey].includes(cellKey) && mainKey !== cellKey)
return true;
}
return false;
}
function submitTimetableEntry() {
const day = document.getElementById("daySelect").value;
if (!day) { alert("Please select a day."); return; }
const optionType = document.getElementById("optionType").value;
if (!optionType) { alert("Please select an option type."); return; }
let entry = "";
if (optionType === "subject") {
const subj = document.getElementById("subjectSelect").value;
if (!subj) { alert("Please select a subject."); return; }
entry = subj;
const timeIdx = document.getElementById("timeSlotSelect").value;
if (timeIdx === "") { alert("Please select a time slot."); return; }
updateTimetableCell(day, parseInt(timeIdx), entry, optionType);
} else if (optionType === "specSubject") {
const spec = document.getElementById("specSubjectSelect").value;
if (!spec) { alert("Please select a specialization subject."); return; }
entry = spec;
const timeIdx = document.getElementById("timeSlotSelect").value;
if (timeIdx === "") { alert("Please select a time slot."); return; }
updateTimetableCell(day, parseInt(timeIdx), entry, optionType);
} else if (optionType === "practical") {
const prac = document.getElementById("practicalSelect").value;
if (!prac) { alert("Please select a practical subject."); return; }
const batch = document.getElementById("batchSelect").value;
if (!batch) { alert("Please select a batch."); return; }
entry = "Batch " + batch + ": " + prac;
const slotIdx = document.getElementById("timeSlotSelect").value;
if (slotIdx === "") { alert("Please select a time slot."); return; }
const indices = practicalTimeSlots[parseInt(slotIdx)].indices;
updateTimetableCellPractical(day, indices, entry);
}
autoSaveTimetable();
closeModal();
generateTable();
}
// For one-hour updates (for specialization subjects, append if same cell).
function updateTimetableCell(day, timeIndex, content, optionType) {
const dayIdx = days.indexOf(day);
const cellKey = timeIndex + "-" + dayIdx;
const key = cellKey + "-" + day;
if (timetableData[key] && optionType === "specSubject") {
if (!timetableData[key].includes(content))
timetableData[key] += "
" + content;
} else {
timetableData[key] = content;
}
}
// For practical subject: update two adjacent cells (merged).
function updateTimetableCellPractical(day, indices, content) {
const dayIdx = days.indexOf(day);
const mainCellKey = indices[0] + "-" + dayIdx;
const key = mainCellKey + "-" + day;
let current = timetableData[key] || "";
const [newBatchLabel, newSubject] = content.split(":").map(x => x.trim());
if (current) {
const entries = current.split("
");
const existingBatches = entries.map(entry => entry.split(":")[0].trim());
if (existingBatches.includes(newBatchLabel)) {
alert(`Cannot add: Batch ${newBatchLabel} already exists in this block.`);
return;
}
timetableData[key] += "
" + content;
} else {
timetableData[key] = content;
}
const mergedGroup = indices.map(idx => idx + "-" + dayIdx);
mergedCells[mergedGroup[0]] = mergedGroup;
}
// ------ Save/Load Timetable Functions ------
function autoSaveTimetable() {
saveTimetable();
}
function saveTimetable() {
const data = {};
timeSlots.forEach((slot, rowIdx) => {
data[slot] = {};
days.forEach((day, colIdx) => {
const cellKey = rowIdx + "-" + colIdx;
const key = cellKey + "-" + day;
if (breakSlots.hasOwnProperty(rowIdx)) {
data[slot][day] = breakSlots[rowIdx];
} else {
data[slot][day] = timetableData[key] || "";
}
});
});
const payload = {
class: teacherClass,
timetable: data
};
fetch('/save', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
})
.then(res => res.json())
.then(res => alert(res.message));
}
function loadTimetable() {
if (!teacherClass) return;
fetch('/load?class=' + teacherClass)
.then(res => res.json())
.then(data => {
timetableData = {};
for (const slot in data) {
for (const day in data[slot]) {
const rowIdx = timeSlots.indexOf(slot);
const colIdx = days.indexOf(day);
if (rowIdx >= 0 && colIdx >= 0) {
const key = rowIdx + "-" + colIdx + "-" + day;
timetableData[key] = data[slot][day];
}
}
}
inferMergedCells();
generateTable();
});
}
function downloadExcel() {
if (!teacherClass) {
alert("Please select a class.");
return;
}
window.location.href = `/download_excel?class=${teacherClass}`;
}
window.onload = function() {
generateTable();
}