PhoenixKitProjects.Web.AssigneeFilter (PhoenixKitProjects v0.18.0)

Copy Markdown View Source

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).

Summary

Functions

How many filters are active — badges the funnel button.

The resolved scopes of the picked person chips.

Default assigns for a consuming LiveView's mount.

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

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.

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.

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).

Functions

active_count(assigns)

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

How many filters are active — badges the funnel button.

current_scopes(assigns)

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

The resolved scopes of the picked person chips.

defaults()

@spec defaults() :: keyword()

Default assigns for a consuming LiveView's mount.

events()

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

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

match_any(assignment, scopes)

@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(socket)

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(socket, arg2, params)

@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).