# `PhoenixKitProjects.Web.AssigneeFilter`
[🔗](https://github.com/BeamLabEU/phoenix_kit_projects/blob/v0.21.2/lib/phoenix_kit_projects/web/assignee_filter.ex#L1)

Shared state glue for the calendar assignee/overdue filter — the chip-rail
model (person chips via the core search_picker, a Me quick-toggle, an
Unassigned lens, Personal-only and Overdue-only refinements, one Clear).

Two LiveViews consume it (the Overview Tasks calendar and the per-project
Calendar tab), each with its own item semantics — the Overview filters
flattened leaf tasks, the project tab filters top-level bars with
descendant-aware matching. This module owns everything that must NOT drift
between them: the assigns, the event handling (including the picker's
search/pick/staged contract), scope resolution, and union matching.

## Usage

    # mount
    socket = socket |> assign(AssigneeFilter.defaults()) |> ...

    # one handle_event clause forwards every filter event
    def handle_event(event, params, socket) when event in @assignee_filter_events do
      case AssigneeFilter.update(socket, event, params) do
        {socket, :reapply} -> {:noreply, apply_my_filter(socket)}
        {socket, :noop} -> {:noreply, socket}
      end
    end

with `@assignee_filter_events AssigneeFilter.events()`. The panel UI lives
in `<.assignee_filter_panel>` (`Web.Components.AssigneeFilterPanel`).

# `active_count`

```elixir
@spec active_count(map()) :: non_neg_integer()
```

How many filters are active — badges the funnel button.

# `current_scopes`

```elixir
@spec current_scopes(map()) :: [PhoenixKitProjects.Assignees.scope()]
```

The resolved scopes of the picked person chips.

# `defaults`

```elixir
@spec defaults() :: keyword()
```

Default assigns for a consuming LiveView's mount.

# `events`

```elixir
@spec events() :: [String.t()]
```

The filter's event names — guard the forwarding handle_event clause with these.

# `match_any`

```elixir
@spec match_any(
  struct(),
  [PhoenixKitProjects.Assignees.scope()]
) :: :direct | {:team, String.t()} | {:department, String.t()} | nil
```

Matches one assignment against every selected scope; a `:direct` hit for
anyone wins (so direct-only keeps a task any selected person holds
personally), otherwise the first inherited provenance labels the row.

# `resolve_me`

```elixir
@spec resolve_me(Phoenix.LiveView.Socket.t()) :: Phoenix.LiveView.Socket.t()
```

Resolves the viewer's own scope once per mount (`:unresolved` → scope | nil).
Call from the consumer's data-load path; nil hides the Me quick-adder.

# `update`

```elixir
@spec update(Phoenix.LiveView.Socket.t(), String.t(), map()) ::
  {Phoenix.LiveView.Socket.t(), :reapply | :noop}
```

Applies one filter event to the socket. Returns `{socket, :reapply}` when
the consumer must re-derive its filtered view, `{socket, :noop}` otherwise
(picker searches answer via push_event and change nothing else).

---

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