Roll reporting up past an absent manager, and say so on both sides
While somebody is on a long-term absence their reports report to the next
management level, and it keeps rolling up until it reaches somebody present.
Derived at read time in lib/acting-manager.ts rather than written to
employees.manager_id: the absent person stays formally in charge, so the
stand-in has to be visible as a stand-in rather than quietly replacing them.
Both ids therefore travel to the UI, and both sides carry a badge — the
absent person ("Abwesend · Vertretung: X") and anyone now reporting
elsewhere ("Vertretung für Y").
Three cases the walk has to survive, all covered by tests:
- Several absent levels in a row — it keeps climbing, and still names the
*recorded* manager as the one being covered for, not the level skipped.
- Everyone above absent — it stops and keeps the recorded manager. Re-rooting
a team to the top of the chart would distort more than showing an absent
manager whose absence is labelled anyway.
- A manager_id cycle, which nothing in the schema forbids.
An absent lead needs no separate deputy field: their stand-in is simply
their own acting manager, the same one their reports moved to.
This commit is contained in:
@@ -39,7 +39,7 @@ const KIND_SHELL: Record<ChartNodeKind, string> = {
|
||||
// required, not just tidy, to keep that smooth at a few hundred nodes.
|
||||
export const OrgChartNode = memo(function OrgChartNode({ id, data }: NodeProps<OrgChartRFNode>) {
|
||||
const { chartNode, expanded, hasChildren, childCount, onToggle } = data;
|
||||
const { kind, label, sublabel, avatar, href, vacant, totalReports } = chartNode;
|
||||
const { kind, label, sublabel, avatar, href, vacant, totalReports, absent, coveredBy, coveringFor } = chartNode;
|
||||
const isMatch = chartNode.matched ?? false;
|
||||
const { width, height } = NODE_DIMENSIONS[kind];
|
||||
|
||||
@@ -73,10 +73,22 @@ export const OrgChartNode = memo(function OrgChartNode({ id, data }: NodeProps<O
|
||||
{sublabel}
|
||||
</span>
|
||||
)}
|
||||
{totalReports !== undefined && totalReports > 0 && (
|
||||
<span className="mt-0.5 text-[10px] font-semibold uppercase tracking-wide text-ink-muted">
|
||||
{childCount} direkt · {totalReports} gesamt
|
||||
{/* The stand-in replaces the report count rather than crowding in
|
||||
next to it: on a card this size the arrangement is the more
|
||||
important of the two, and it only appears while one is in force. */}
|
||||
{absent ? (
|
||||
<span className="mt-0.5 truncate text-[10px] font-semibold text-warning-text">
|
||||
Abwesend{coveredBy ? ` · Vertretung: ${coveredBy}` : ""}
|
||||
</span>
|
||||
) : coveringFor ? (
|
||||
<span className="mt-0.5 truncate text-[10px] font-semibold text-info-text">Vertretung für {coveringFor}</span>
|
||||
) : (
|
||||
totalReports !== undefined &&
|
||||
totalReports > 0 && (
|
||||
<span className="mt-0.5 text-[10px] font-semibold uppercase tracking-wide text-ink-muted">
|
||||
{childCount} direkt · {totalReports} gesamt
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user