/admin/org-chart) is the editing surface for the
three-level people hierarchy that the rest of the TeleQuick admin console
reads from. It renders one card per team manager, a grid of team-leader cards
inside it, and agent counts rolled up from the agent dimension.
Everything on the screen is backed by three dimension tables and four tRPC
routers:
The three levels and what owns what
The hierarchy is TM → TL → agents, and each link is a single nullable foreign key on the child row:- A team leader points at its manager with
team_manager_id. If that column isnull, the leader has no manager. - An agent points at its leader with
team_leader_id. The org chart never writes this column — it only reads it to count agents.
total returned by adminAgents.list.
Record fields
Team managers and team leaders share the same shape; a team leader adds one column.tRPC procedures behind the inline CRUD
Every procedure is scoped by
orgId, taken from the active org in the tenant
context. If no org is active, the screen issues no queries at all.
List procedures return { rows, total }. The org chart loads the hierarchy in
one shot rather than paginating: it requests up to 200 sites, 500 team managers,
500 team leaders, and 5000 agents. Sites are cached for 60 s and agents for
30 s; manager and leader lists are not cached, so they re-fetch on mount.
The manager and leader lists are requested with includeInactive: true, which
is why deactivated people still appear on the chart with an Inactive chip.
After a successful create, update, or remove, the drawer invalidates the
corresponding list query (adminTeamManagers.list or
adminTeamLeaders.list) and closes. Agent counts refresh on their own cache
schedule.
Creating and reassigning team leaders
There are three entry points into the team-leader form, and they differ only in what they prefill:- New TL in the page header — opens an empty form with no site and no
manager. Submitting it creates a leader with
team_manager_id: null, which lands in the unassigned bucket. - Add TL on a manager card — prefills
team_manager_idwith that manager’s id andsite_idwith that manager’s site. Both remain editable before you save. - Clicking a team-leader card — opens the same form loaded with that leader’s row, in update mode.
adminTeamLeaders.update
with a new team_manager_id. Because the grouping is derived from that column
on every render, the leader’s card moves to the new manager’s card as soon as
the list query is invalidated. Agents follow their leader automatically — their
team_leader_id is untouched, so their counts move with the card.
The distinction between create and update is made by whether the row carries an
id. The “Add TL” path passes a synthetic row with id: '', which the form
treats as a create.
Deactivation is a soft delete
Theremove procedures on both routers are soft deletes. The confirm dialog and
the success toast both say Deactivate / Deactivated, not “delete”, and
the row stays queryable via includeInactive: true.
For a team manager, the confirmation spells out the effect on children:
Soft-deleted. Their TLs stay but lose the team_manager_id link until
reassigned.
So deactivating a manager:
- keeps every team-leader row intact and active,
- clears the
team_manager_idlink on those leaders, which moves them into the Unassigned team leaders card, - leaves agents untouched — they still point at the same team leaders.
active: true in the update patch. The Active
checkbox is only rendered when editing an existing row.
Sites filtering and unassigned leaders
The single filter on the screen is a Site chip combobox, populated fromadminSites.list (label = site name, hint = site code). Clearing it returns to
All sites.
The filter is applied client-side and independently at both levels:
- Team managers are kept when
tm.site_id === filterSite. - Team leaders are kept when
tl.site_id === filterSite.
— in that position.
Unassigned team leaders is a synthetic card, not a record. It collects every
leader whose team_manager_id is null (after site filtering) and renders only
when that bucket is non-empty. It has no avatar, no edit button, and no tags —
there is no manager row to attach them to. The fix is stated inline on the
card: edit each leader and assign a manager.
Agents whose team_leader_id is null are bucketed separately when counts are
computed, but that bucket has no card on this screen, so those agents appear in
the header total and in no per-card count.
Tagging team managers
Team managers are taggable with the shared tag system, under the resource kinddim_team_manager. Tags render as a TagCell on each manager row and can be
added or removed inline.
Tag resolution is deliberately batched: the screen calls useResourceTags once
for the ids of all currently visible managers and passes each card its slice.
Cards only write. A write calls the hook’s refresh, which re-resolves the
whole visible set rather than one row.
Team leaders and agents are not tagged from this screen.
Agent counts and the directory link
Agent counts are derived, never stored on the leader. The screen pullsadminAgents.list and buckets rows by team_leader_id, splitting each bucket
by the agent’s kind:
kind: 'human'increments the human count,- anything else increments the LLM count.
<n> human. The LLM chip renders only when
that leader has at least one LLM agent. The manager card shows the sum of both
kinds across its leaders, alongside the leader count.
Each team-leader card carries a View → link to
/admin/directory?tl=<team_leader_id>. This is the drill-down path from the
structural view to the per-agent roster: the org chart tells you who owns whom,
and the directory, filtered by that tl query parameter, tells you who is on
that team.
Related
- Authentication — the API key that authorises these control-plane calls
- Telemetry —
agent_idon CDRs, which is how call data joins back todim_agent