# `PhoenixKitProjects.People`
[🔗](https://github.com/BeamLabEU/phoenix_kit_projects/blob/v0.21.2/lib/phoenix_kit_projects/people.ex#L1)

The ONE doorway to people/team/department data for this module — the
staff-optional seam (Phase B of the hub rework, panel-approved design).

## Why shadow schemas, not a bridge

The staff DB tables are created by CORE's migrations (V100), not by the
`phoenix_kit_staff` package — staff ships no migrations; it is the
admin UI + context over core-owned tables. So the tables (and projects'
SQL FKs to them) exist on every install, and projects can keep its
`belongs_to` graph, preloads, authz relationship grants, and direct
assignee queries by mapping its OWN minimal read-only schemas
(`PhoenixKitProjects.People.{Person,Team,Department,TeamMembership}`)
over those tables. The staff PACKAGE is optional: it is the people
ADMIN surface, not the data's owner.

## Rules

  * Read-only: no changesets, no writes — people are managed in staff.
  * Filter semantics COPIED from staff, not imported: soft-deleted rows
    are `status == "trashed"` and excluded from listings by default.
  * Label semantics mirrored from staff: `display_name/1` resolves
    name → user's first/last name → user email → "Unnamed";
    team/department names localize via the row's `translations` map.
  * `staff_admin_available?/0` gates ONLY admin-UI affordances (manage-
    people links, the "Me" chip's person resolution when staff adds
    value) — data reads never require the staff package.

# `display_name`

```elixir
@spec display_name(PhoenixKitProjects.People.Person.t() | nil) :: String.t()
```

name → user's first/last → user email → "Unnamed" (staff parity).

# `get_person`

```elixir
@spec get_person(
  binary(),
  keyword()
) :: PhoenixKitProjects.People.Person.t() | nil
```

A person by uuid with `[:user, :primary_department]` preloaded, or nil.

# `get_person_by_user_uuid`

```elixir
@spec get_person_by_user_uuid(
  binary(),
  keyword()
) :: PhoenixKitProjects.People.Person.t() | nil
```

The person linked to a core user, or nil. STAFF-PARITY: resolves
trashed people too (soft-delete keeps assignments/memberships live so
restore is clean — filtering here blanked My Tasks and the Me chip for
the whole trash→restore window; panel round). Only LISTINGS exclude
trashed.

# `list_departments`

```elixir
@spec list_departments() :: [PhoenixKitProjects.People.Department.t()]
```

All departments, name-ordered.

# `list_memberships_for_person`

```elixir
@spec list_memberships_for_person(binary()) :: [
  PhoenixKitProjects.People.TeamMembership.t()
]
```

A person's team memberships with `team: [:department]` preloaded.

# `list_people`

```elixir
@spec list_people(keyword()) :: [PhoenixKitProjects.People.Person.t()]
```

Non-trashed people ordered by display name source, `[:user]` preloaded
(the option loaders read the user email). Mirrors staff's default
trashed-exclusion.

# `list_teams`

```elixir
@spec list_teams() :: [PhoenixKitProjects.People.Team.t()]
```

All teams with their department preloaded, name-ordered.

# `localized_name`

```elixir
@spec localized_name(
  PhoenixKitProjects.People.Team.t()
  | PhoenixKitProjects.People.Department.t()
  | nil,
  String.t() | nil
) :: String.t() | nil
```

A team/department's localized name: `translations[lang]["name"]` else the primary.

# `staff_admin_available?`

```elixir
@spec staff_admin_available?() :: boolean()
```

Whether the staff PACKAGE (the people admin surface) is installed and
enabled — for UI affordances only; the data paths below never need it.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
