DurableDashboard.Components.Data.DataTable (DurableDashboard v0.1.0-rc)

Copy Markdown View Source

Reusable data-table LiveComponent. Owns its own filter / sort / page state, drives data via a parent-supplied :fetcher function, and emits query-change notifications to the parent LV so the parent can update the URL via live_patch. Real-time updates from the parent are triggered with Phoenix.LiveView.send_update(__MODULE__, id: "...", refresh: true).

Why a LiveComponent

Three list views (Workflows, Schedules, Inputs) need the same filter/sort/ pagination machinery. Extracting it as a function component would force every parent to hold the state itself, defeating the point. As a stateful component it owns the state once and the parents stay declarative.

Mount-time assigns from the parent

assignrequireddescription
:idyesUnique component id (also used for stream key)
:fetcheryes(query :: map -> {rows :: list, total :: integer})
:columnsyesList of column specs (see below)
:filtersnoList of filter definitions (see below); default []
:queryyesCurrent query — usually parsed from URL by the parent
:row_idno(row -> id_string) for stream identity (default: & &1.id)
:row_navigateno(row -> url_string) — clicking a row live-navigates here
:per_pagenodefault 20
:empty_titlenoempty-state title (default "No results")
:empty_descriptionnoempty-state subtitle (optional)
:empty_iconnoempty-state icon (optional)
:search_placeholdernosearch input placeholder (default "Search…")

Column spec

%{
  key: :status,                # atom — accessible via Map.get(row, key) | row[to_string(key)]
  label: "Status",
  sortable?: false,
  class: nil,                  # optional td class
  render: &my_render/1         # (row -> rendered HEEx) — required
}

Filter spec

%{
  key: :status,                # atom or string — keyed into query map
  type: :select | :search,
  label: "Status",
  options: ["", "running", "failed"],     # for :select, [""] is "all"
  placeholder: nil
}

Query map shape

%{
  page: integer,                  # 1-based
  search: string | nil,           # text search
  sort_by: atom | nil,
  sort_dir: :asc | :desc,
  # plus any filter keys, e.g. status: "running"
}

Communication

  • LC → parent: when query changes, the LC sends a regular Erlang message {:data_table, component_id, :query_changed, new_query} to the parent LV pid (captured at mount). The parent decides whether to live_patch or update assigns directly.
  • Parent → LC: new :query via send_update(__MODULE__, id: id, query: q) re-fetches automatically. :refresh -> true re-fetches the current query.