}
/>
{/* C5: month calendar grid */}
{/* On-call standings — moved here from the Reports tab so all
on-call surface area lives together. The component itself is
still defined in page-reports.jsx (window.OnCallStandingsSection)
to avoid duplicating ~140 lines of fetch + render logic. */}
{typeof window.OnCallStandingsSection === 'function' && (
React.createElement(window.OnCallStandingsSection)
)}
{/* Active section */}
s))("Currently on-call")}
tip={(window.appT||(s=>s))("Primary + secondary on-call right now (per the active rotation). Auto-resolves on every ticket landing.")} sub={window.appLang && window.appLang() === 'ar'
? `${data.current.length} مناوبة نشطة`
: `${data.current.length} active rotation${data.current.length === 1 ? '' : 's'}`} />
{data.current.length === 0 ? (
} title={(window.appT||(s=>s))("Nobody on-call right now")} subtitle={(window.appT||(s=>s))("Add a rotation below to assign someone.")} />
) : (
{(window.appT||(s=>s))("Team")}
{(window.appT||(s=>s))("Person")}
{(window.appT||(s=>s))("Role")}
{(window.appT||(s=>s))("Coverage")}
{(window.appT||(s=>s))("Notes")}
{data.current.map(r => renderRow(r))}
)}
{/* Upcoming section */}
s))("Upcoming (next 30 days)")}
tip={(window.appT||(s=>s))("Next 30 days of on-call shifts. Edit any cell to override (admin only).")} sub={window.appLang && window.appLang() === 'ar'
? `${data.upcoming.length} مجدولة`
: `${data.upcoming.length} scheduled`} />
{data.upcoming.length === 0 ? (
} title={(window.appT||(s=>s))("No upcoming rotations")} subtitle={(window.appT||(s=>s))("Schedule the next rotation below.")} />
) : (
{(window.appT||(s=>s))("Team")}
{(window.appT||(s=>s))("Person")}
{(window.appT||(s=>s))("Role")}
{(window.appT||(s=>s))("Coverage")}
{(window.appT||(s=>s))("Notes")}
{data.upcoming.map(r => renderRow(r))}
)}
{/* Add rotation form */}
s))("Add rotation")}
tip={(window.appT||(s=>s))("Single-shift add form. For bulk uploads use 'Import CSV' (#31). 'Export CSV' is available on the same page for round-tripping.")} sub={(window.appT||(s=>s))("Manager+ only")} />
);
}
// C5: month calendar grid with primary + secondary on each day.
function OnCallCalendarCard({ shifts, employees }) {
const today = new Date();
const [month, setMonth] = useState({ y: today.getFullYear(), m: today.getMonth() });
const monthStart = new Date(month.y, month.m, 1);
const monthEnd = new Date(month.y, month.m + 1, 0);
const startWeekday = monthStart.getDay(); // 0=Sun
const daysInMonth = monthEnd.getDate();
const empById = Object.fromEntries(employees.map(e => [e.id, e]));
// Build per-day list of EVERY assigned person — was previously primary+
// single secondary, which collapsed multi-tier fallback chains
// (bassel→miro→hany) down to whoever happened to be written last and
// looked like "only Hany". Now: bucket by primary / backup / escalation
// and stack them all in the cell so the full chain is visible.
const byDay = {};
for (const s of (shifts || [])) {
const sd = new Date(s.start_date), ed = new Date(s.end_date);
for (let d = new Date(sd); d <= ed; d.setDate(d.getDate() + 1)) {
if (d.getMonth() !== month.m || d.getFullYear() !== month.y) continue;
const key = d.getDate();
const role = (s.role || (s.is_primary ? 'primary' : 'backup'));
const slot = (byDay[key] = byDay[key] || { primary: [], backup: [], escalation: [] });
// Skip placeholder pre-imports — they're real OnCallShift rows but
// have no real engineer yet (mxid starts with @_oncall-pending-).
const emp = empById[s.user_id];
const isPlaceholder = (emp?.matrix_id || '').startsWith('@_oncall-pending-');
if (isPlaceholder) continue;
const bucket = slot[role] || slot.backup;
// Dedupe by user_id so the chain row + a weekly primary row that
// refer to the same person don't render twice.
if (!bucket.find(x => x.user_id === s.user_id)) bucket.push(s);
}
}
const cells = [];
for (let i = 0; i < startWeekday; i++) cells.push(null);
for (let d = 1; d <= daysInMonth; d++) cells.push(d);
while (cells.length % 7 !== 0) cells.push(null);
const monthName = monthStart.toLocaleDateString(undefined, { month: 'long', year: 'numeric' });
return (
s))("On-call calendar")}
tip={(window.appT||(s=>s))("Visual month view of primary + secondary on-call assignments. Click a shift to jump to its details. Use ← → to change months.")}
sub={monthName}
right={