import { useState } from 'react'
import { Archive, Crown, Plus, UserMinus, Users2 } from 'lucide-react'
import { Panel } from '../components/ui'
import { Empty, Note, Select, btnGhost, btnPrimary, fieldCls } from '../components/form'
import { CAP, can } from '../lib/can'
import { useAuth } from '../lib/useAuth'
import {
useAddTeamMember,
useCreateTeam,
useOrgSettings,
useRemoveTeamMember,
useTeams,
useUpdateTeam,
} from '../lib/settingsQueries'
import type { Team } from '../lib/settings'
// TEAMS, on the accounts screen rather than in settings.
//
// A team is an access grant when record scope is narrowed, so it belongs beside the roster that grants
// roles, not beside the toggles. The server agrees: reading teams needs candidate.read, writing them needs
// org.settings.write, the same authority as the setting that gives a team its meaning.
//
// Archived teams are hidden rather than listed greyed out. An archived desk grants nothing, so showing it
// among the live ones invites somebody to add a person to a team that will not widen what they can see.
export function TeamsPanel({ people }: { people: { id: string; full_name: string; role: string; status: string }[] }) {
const { principal } = useAuth()
const teams = useTeams()
const settings = useOrgSettings()
const create = useCreateTeam()
const [newName, setNewName] = useState('')
const mayWrite = can(principal, CAP.orgSettings)
const all = teams.data?.teams ?? []
const active = all.filter((t) => t.status === 'active')
const archivedCount = all.length - active.length
// Why teams matter right now, stated from the settings actually in force rather than in the abstract. An
// owner who has not narrowed anything is told that plainly, so they do not build an org chart the product
// is currently ignoring.
const s = settings.data?.settings
const scopeNarrowed = !!s && (s.staff_record_scope === 'team' || s.manager_record_scope === 'team')
const visibilityNarrowed = !!s && s.work_visibility === 'team'
const sensitivityNarrowed =
!!s && [s.rate_visibility, s.margin_visibility, s.contact_visibility].includes('team')
const teamsMatter = scopeNarrowed || visibilityNarrowed || sensitivityNarrowed
if (teams.isError) {
return (
The teams could not be read. {(teams.error as Error)?.message}
)
}
return (
{s && !teamsMatter && (
Nothing is currently narrowed to a team, so these are an org chart rather than an access control.
They start deciding what people see the moment record scope, work visibility or a sensitivity is
set to a team in settings.
)}
{scopeNarrowed && (
Record scope is narrowed to a team, so somebody on no team sees only their own records. Every
person who works a desk needs to be on one.
)}
)
}
function TeamRow({
team,
people,
mayWrite,
}: {
team: Team
people: { id: string; full_name: string; role: string; status: string }[]
mayWrite: boolean
}) {
const add = useAddTeamMember()
const remove = useRemoveTeamMember()
const update = useUpdateTeam()
const [confirmArchive, setConfirmArchive] = useState(false)
const memberIDs = new Set(team.members.map((m) => m.user_id))
// Only ACTIVE accounts can be added. A deactivated account cannot sign in, so putting one on a desk would
// be a grant to nobody and would make the desk look larger than it is.
const addable = people.filter((p) => !memberIDs.has(p.id) && p.status === 'active')
// A lead has to be ON the desk. Offering somebody who is not would create a lead whose own scope does not
// include the team they lead.
const leadChoices = [
{ value: '', label: 'No lead' },
...team.members.map((m) => ({ value: m.user_id, label: m.name })),
]
return (
{/* Archiving is stated before it happens, because it can silently narrow what the people on it see. */}
{confirmArchive && (
Archiving this desk removes the access it grants. If record scope is narrowed to a team, everybody
on it falls back to their own records until they are on another desk.