PhoenixKitProjects.Web.ProjectShowLive (PhoenixKitProjects v0.23.2)

Copy Markdown View Source

Show a project with a vertical timeline of assignments. Supports inline status changes, duration editing, dependency management, and tracks who completed each task.

List / Timeline / Calendar tabs

The page has three views — the vertical task list, the embedded ProjectGanttLive Timeline, and the embedded ProjectCalendarLive month calendar — toggled by tabs under the shared header. Switching is instant (an assign flip) and each nested LV is lazily mounted on first open, then kept (so the gantt's zoom/expand and the calendar's month navigation survive switching back). The tabs render in every mount context, embedded live_render renders included (only templates stay list-only). Note this means the Timeline/Calendar tabs are nested live_renders, which are themselves nested LVs when the show page is embedded — deliberate, server-rendered, so both views show even before any JS loads.

The page is a set of TOP-LEVEL tabs — Tasks (List / Board / Timeline / Calendar behind its own, subordinate strip), one per enabled extension that contributes a tab (Events, Whiteboards, …) and Comments (the project's own thread, switched by the Discussions extension). Each stands alone in an otherwise empty project, so a project can be only a whiteboard or only a discussion. The header stays: it is the project, not a tab. Templates keep the Tasks tab alone, without the strip.

Keeping the URL in sync (/tasks/board, /whiteboards, /comments) is optional and off by default: it's only on when @tab_url_sync? is true, which the router-mounted standalone admin page sets (so its deep-linking keeps working) and an embed can opt into via session["tab_url_sync"]. When on, the strip carries the active tab's canonical address in data-url and core's PkUrlMirror hook REPLACES the browser's URL with it on every render — deep links / copy / reload land on the right tab, while back/forward return to the previous page. Deliberately no per-tab history entries: they'd need this LV to export handle_params/3 (which would block live_render embedding), and LiveView's popstate handler treats foreign pushState entries as live navigation — remounting and crashing on the missing callback. With sync off the tabs still switch fully — they just never touch the host page's URL.

Summary

Functions

The page's identity controls — the workflow-status picker and the ⋮ menu (Edit, Members, Files, Activity, Set health, Archive). On the standalone admin page core renders this in the site header next to the project's name (page_toolbar: {__MODULE__, :header_toolbar}, the LiveView's own assigns, events land here as usual); an embed has no site header, so its body renders the same component under its h1. Never both — the form id is unique either way.

The canonical address of a tab — what the page hands core's PkUrlMirror so the browser's URL follows a switch (router mounts only). The task views live under /tasks (/tasks/board|timeline|calendar), Comments at /comments, a contributed extension tab at /<its key>; an unknown tab (unreachable after resolution) falls back to the bare project page.

The TOP-LEVEL tab a view belongs to: the four task views (:list, :board, :gantt, :calendar) are :tasks; everything else — a contributed extension tab ("ext:<ext>:<tab>"), :comments — is itself.

Functions

header_toolbar(assigns)

The page's identity controls — the workflow-status picker and the ⋮ menu (Edit, Members, Files, Activity, Set health, Archive). On the standalone admin page core renders this in the site header next to the project's name (page_toolbar: {__MODULE__, :header_toolbar}, the LiveView's own assigns, events land here as usual); an embed has no site header, so its body renders the same component under its h1. Never both — the form id is unique either way.

tab_url(id, project, ext_tabs)

@spec tab_url(atom() | String.t(), map(), [map()]) :: String.t()

The canonical address of a tab — what the page hands core's PkUrlMirror so the browser's URL follows a switch (router mounts only). The task views live under /tasks (/tasks/board|timeline|calendar), Comments at /comments, a contributed extension tab at /<its key>; an unknown tab (unreachable after resolution) falls back to the bare project page.

top_tab_of(tab)

@spec top_tab_of(atom() | String.t()) :: atom() | String.t()

The TOP-LEVEL tab a view belongs to: the four task views (:list, :board, :gantt, :calendar) are :tasks; everything else — a contributed extension tab ("ext:<ext>:<tab>"), :comments — is itself.