feat(calendar): N moderator slots per day with disable-on-duplicate
This commit is contained in:
1 parent
3b84fce1e6
commit
fa28640c28
1 file changed
+37
-14
+37
-14
@@ -140,8 +140,18 @@ function renderGrid(wrapper, exportBtn) {
|
|||||||
wrapper.replaceChildren();
|
wrapper.replaceChildren();
|
||||||
|
|
||||||
const daysInMonth = new Date(currentYear, currentMonth, 0).getDate();
|
const daysInMonth = new Date(currentYear, currentMonth, 0).getDate();
|
||||||
const nameById = Object.fromEntries(state.employees.map(e => [e.id, e.name]));
|
const empNameById = Object.fromEntries(state.employees.map(e => [e.id, e.name]));
|
||||||
const planByDate = Object.fromEntries(currentPlan.map(a => [a.date, a.employeeId]));
|
const entriesByDate = {};
|
||||||
|
for (const a of currentPlan) {
|
||||||
|
if (!entriesByDate[a.date]) entriesByDate[a.date] = [];
|
||||||
|
entriesByDate[a.date].push(a);
|
||||||
|
}
|
||||||
|
// Sort each date's entries alphabetically by employee name (stable slot order)
|
||||||
|
for (const date of Object.keys(entriesByDate)) {
|
||||||
|
entriesByDate[date].sort((x, y) =>
|
||||||
|
(empNameById[x.employeeId] ?? '').localeCompare(empNameById[y.employeeId] ?? '', 'de')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Day-of-week header row
|
// Day-of-week header row
|
||||||
const headerGrid = document.createElement('div');
|
const headerGrid = document.createElement('div');
|
||||||
@@ -174,7 +184,6 @@ function renderGrid(wrapper, exportBtn) {
|
|||||||
const isWeekend = wdNum >= 6;
|
const isWeekend = wdNum >= 6;
|
||||||
const isHoliday = state.calendar.holidays.includes(iso);
|
const isHoliday = state.calendar.holidays.includes(iso);
|
||||||
const isClosure = state.calendar.companyClosures.includes(iso);
|
const isClosure = state.calendar.companyClosures.includes(iso);
|
||||||
const assigneeId = planByDate[iso];
|
|
||||||
|
|
||||||
const cell = document.createElement('div');
|
const cell = document.createElement('div');
|
||||||
cell.className = 'cal-day';
|
cell.className = 'cal-day';
|
||||||
@@ -210,8 +219,17 @@ function renderGrid(wrapper, exportBtn) {
|
|||||||
tag.appendChild(removeBtn);
|
tag.appendChild(removeBtn);
|
||||||
cell.appendChild(tag);
|
cell.appendChild(tag);
|
||||||
} else {
|
} else {
|
||||||
// Assignee dropdown
|
// N slots per day
|
||||||
|
const slotCount = state.settings.moderatorsPerDay;
|
||||||
|
const dayEntries = entriesByDate[iso] ?? []; // already alphabetically sorted
|
||||||
|
const slotValues = []; // employeeId per slot index, '' if empty
|
||||||
|
for (let s = 0; s < slotCount; s++) {
|
||||||
|
slotValues.push(dayEntries[s]?.employeeId ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let slotIdx = 0; slotIdx < slotCount; slotIdx++) {
|
||||||
const sel = document.createElement('select');
|
const sel = document.createElement('select');
|
||||||
|
sel.dataset.slot = String(slotIdx);
|
||||||
|
|
||||||
const emptyOpt = document.createElement('option');
|
const emptyOpt = document.createElement('option');
|
||||||
emptyOpt.value = '';
|
emptyOpt.value = '';
|
||||||
@@ -222,26 +240,31 @@ function renderGrid(wrapper, exportBtn) {
|
|||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = emp.id;
|
opt.value = emp.id;
|
||||||
opt.textContent = emp.name; // textContent: safe
|
opt.textContent = emp.name; // textContent: safe
|
||||||
if (emp.id === assigneeId) opt.selected = true;
|
// Disable if already picked in a different slot for this day
|
||||||
|
const pickedElsewhere = slotValues.some((id, i) => i !== slotIdx && id === emp.id);
|
||||||
|
if (pickedElsewhere) opt.disabled = true;
|
||||||
|
if (emp.id === slotValues[slotIdx]) opt.selected = true;
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
sel.addEventListener('change', () => {
|
sel.addEventListener('change', () => {
|
||||||
const empId = sel.value;
|
const newId = sel.value;
|
||||||
if (empId) {
|
const oldId = slotValues[slotIdx];
|
||||||
const entry = { date: iso, employeeId: empId, manual: true };
|
if (oldId) {
|
||||||
|
removeHistoryEntry(state, iso, oldId);
|
||||||
|
currentPlan = currentPlan.filter(a => !(a.date === iso && a.employeeId === oldId));
|
||||||
|
}
|
||||||
|
if (newId) {
|
||||||
|
const entry = { date: iso, employeeId: newId, manual: true };
|
||||||
addHistoryEntries(state, [entry]);
|
addHistoryEntries(state, [entry]);
|
||||||
const idx = currentPlan.findIndex(a => a.date === iso);
|
currentPlan.push(entry);
|
||||||
if (idx >= 0) currentPlan[idx] = entry; else currentPlan.push(entry);
|
|
||||||
} else {
|
|
||||||
removeHistoryEntry(state, iso);
|
|
||||||
currentPlan = currentPlan.filter(a => a.date !== iso);
|
|
||||||
}
|
}
|
||||||
saveState();
|
saveState();
|
||||||
exportBtn.style.display = currentPlan.length > 0 ? 'inline-block' : 'none';
|
renderGrid(wrapper, exportBtn);
|
||||||
});
|
});
|
||||||
|
|
||||||
cell.appendChild(sel);
|
cell.appendChild(sel);
|
||||||
|
}
|
||||||
|
|
||||||
// FT / BU mark buttons
|
// FT / BU mark buttons
|
||||||
const btnRow = document.createElement('div');
|
const btnRow = document.createElement('div');
|
||||||
|
|||||||
Reference in new issue
Block a user