Maps projects — and, for the default Tasks mode, their scheduled tasks — to
PhoenixLiveCalendar.Event structs for the Overview dashboard calendar.
Two mapping modes back the Overview calendar's mode toggle:
task_events/3(the Tasks default) — every leaf task across every given project, placed on the days itsScheduleLayoutspan covers, colored by the OWNING project's identity color so a day reads as "what happens today, across projects". Same-day tasks stack as chips, multi-day tasks as bars; the LiveView caps the per-day count and the grid shows a Google-style "+N more" for the rest.events/6(the Projects mode — the original view) — each project as one all-day "ongoing line": running projects span start → planned-end (reaching at least today, so they read as ongoing), completed projects span start → completion, and scheduled projects get a one-day marker on their planned start.
Bars are colored by project identity — a stable color per project, picked
by hashing the project id over a fixed palette of hues spread around the color
wheel — so adjacent and stacked bars stay tellable apart. Lifecycle status is
conveyed by the dashboard's project cards, with one exception: for a running
summary the caller tags late: true, only the stretch of the bar PAST its
planned end (planned_end+1 .. today) is highlighted — via extra.highlight
with the pk-overdue class (it marks that stretch in the INVERSE of the bar's
own colour — diagonal stripes over the identity colour, or a solid fill —
animated per animation_style/0, all configured on /admin/settings/projects)
— so the length of the marked tail shows how late the project is. The rest
keeps its identity color.
end is exclusive in PhoenixLiveCalendar ([start, end)), so the inclusive
last day gets +1. Projects without a placeable start date are dropped.
Summary
Functions
Allowed overdue-animation modes (for the settings form).
Allowed overdue-marker patterns (for the settings form).
Clamp range {lo, hi} for a numeric display field (bounds the form input).
CSS body (no surrounding <style> tag) for an overdue-animation config map of
the shape read/0 returns. Public so tests and a settings preview
can render a given config without touching the settings store.
The <style> block for the overdue animation, from an already-read/0
config map (callers thread the map so one read serves style + legend). Safe to
inject with Phoenix.HTML.raw/1: every interpolated value is a validated enum
or clamped number, never free text.
Builds the calendar event list from the dashboard's already-loaded data.
Events whose [start, end) span covers date, soonest-starting first —
the shared day-popup row source for both calendars.
The exclusive end DATE for a span ending at e (UTC naive). With a viewer
offset ("+3") the end is evaluated in the local frame; nil keeps UTC —
the project Calendar tab's Timeline-parity frame. An end falling exactly on
(local) midnight doesn't occupy that day, any later instant does. Floored
one day past the start so a zero-length span still shows as a one-day chip.
The late-task marker class for a read/0 config: "pattern"
(the default) — the pk-overdue overlay, i.e. the same configured
stripes/solid look the Projects-mode overdue stretch uses, so every
calendar marks lateness identically — or "ring", the red inset ring
alternative. Anything else (including maps missing the key) follows the
default. Consumers using the pattern must also inject animation_style/1
(and ideally wrap the grid in SyncAnimations).
Allowed late-task marker styles (for the settings form).
Tailwind classes giving any of OUR clickable controls in-flight feedback:
LiveView tags the clicked element with phx-click-loading for the round
trip; these arbitrary variants pulse + dim it and block double-clicks.
Returned from a function (not inlined per call site) so every control
shares one literal for the Tailwind scan.
A <style> block giving the calendar LIBRARY's clickable nodes (event
chips, multi-day bars, day cells, "+N more") the same in-flight pulse —
their classes are lib-rendered, so the Tailwind-variant approach above
can't reach them. Static CSS, safe for Phoenix.HTML.raw/1.
Per-day caps for the month grids, shared by BOTH calendars — the "+N more"
link / whole-day popup contract depends on the two grids agreeing on when
a day overflows. max_events/0 caps single-day chips, max_multiday/0
caps multi-day bar slots.
Persist one calendar-display field. An invalid field or value is ignored.
Booleans go through put_flag/2; week_start_day is core's setting and is
NOT writable from here.
Flip one boolean display flag ("show_weekends", "show_week_numbers",
"fixed_weeks"). Unknown fields are ignored.
The full calendar-display config (validated, with safe defaults): the grid
appearance half (weekends / week numbers / fixed weeks / per-day caps, plus
the site-wide week_start_day core setting the grids must honor) and the
overdue/late-marker half. One batched, uncached query — this runs on every
Overview reload (each {:projects, …} broadcast), not just mount, and
direct reads keep the settings page's live demo fresh.
Restore every calendar-display setting to its default.
Builds the Tasks-mode event list (+ a per-task metadata map) from prepared
{item, span} pairs — each a PhoenixKitProjects.ScheduleLayout item with
its computed %{start: NaiveDateTime, end: NaiveDateTime} span. Pure: the
caller runs the schedule walk (ScheduleLayout.tree/1 per project) and
passes only the LEAF items (sub-project container assignments excluded —
their child tasks stand for themselves here).
A task is late when it isn't done and its scheduled span ended before
now — the schedule says it should already have finished. The shared rule
behind both calendars' red rings, late badges, and Overdue-only filters
(a nil now flags nothing — pure date math only).
Functions
@spec anim_modes() :: [String.t()]
Allowed overdue-animation modes (for the settings form).
@spec anim_patterns() :: [String.t()]
Allowed overdue-marker patterns (for the settings form).
Clamp range {lo, hi} for a numeric display field (bounds the form input).
CSS body (no surrounding <style> tag) for an overdue-animation config map of
the shape read/0 returns. Public so tests and a settings preview
can render a given config without touching the settings store.
The <style> block for the overdue animation, from an already-read/0
config map (callers thread the map so one read serves style + legend). Safe to
inject with Phoenix.HTML.raw/1: every interpolated value is a validated enum
or clamped number, never free text.
@spec defaults() :: map()
The pure compile-time defaults of read/0 (no settings read; core's
week_start_day falls back to Monday). Mount-time placeholder so a render
can never dot-access a nil config — read/0 overwrites it immediately.
@spec events( [map()], [PhoenixKitProjects.Schemas.Project.t()], [PhoenixKitProjects.Schemas.Project.t()], String.t() | nil, Date.t(), String.t(), keyword() ) :: [PhoenixLiveCalendar.Event.t()]
Builds the calendar event list from the dashboard's already-loaded data.
active_summaries—Projects.project_tree_summary/1maps for running projects (carry:projectand:planned_end), each tagged by the caller with:late(boolean) to drive the blinkcompleted/upcoming—[Project.t()](recently completed / scheduled)lang— content language for project titlestoday— anchor so a running project's bar always reaches at least todayoffset— viewer timezone offset (e.g. "+3"); UTC datetimes are converted to local calendar dates with it, so bars land on the same day the viewer sees
Options:
:late_marker— how a LATE running project is marked ("pattern"default):"pattern"highlights the overdue stretch (planned_end+1 .. today) with the configured overdue pattern, so the marked length shows HOW late;"ring"rings the whole bar instead — the same marker late task chips wear, so one setting dresses everything late the same way.
@spec events_on([PhoenixLiveCalendar.Event.t()], Date.t()) :: [ PhoenixLiveCalendar.Event.t() ]
Events whose [start, end) span covers date, soonest-starting first —
the shared day-popup row source for both calendars.
@spec exclusive_end_date(Date.t(), NaiveDateTime.t(), String.t() | nil) :: Date.t()
The exclusive end DATE for a span ending at e (UTC naive). With a viewer
offset ("+3") the end is evaluated in the local frame; nil keeps UTC —
the project Calendar tab's Timeline-parity frame. An end falling exactly on
(local) midnight doesn't occupy that day, any later instant does. Floored
one day past the start so a zero-length span still shows as a one-day chip.
The late-task marker class for a read/0 config: "pattern"
(the default) — the pk-overdue overlay, i.e. the same configured
stripes/solid look the Projects-mode overdue stretch uses, so every
calendar marks lateness identically — or "ring", the red inset ring
alternative. Anything else (including maps missing the key) follows the
default. Consumers using the pattern must also inject animation_style/1
(and ideally wrap the grid in SyncAnimations).
@spec late_markers() :: [String.t()]
Allowed late-task marker styles (for the settings form).
@spec loading_class() :: String.t()
Tailwind classes giving any of OUR clickable controls in-flight feedback:
LiveView tags the clicked element with phx-click-loading for the round
trip; these arbitrary variants pulse + dim it and block double-clicks.
Returned from a function (not inlined per call site) so every control
shares one literal for the Tailwind scan.
@spec loading_style() :: String.t()
A <style> block giving the calendar LIBRARY's clickable nodes (event
chips, multi-day bars, day cells, "+N more") the same in-flight pulse —
their classes are lib-rendered, so the Tailwind-variant approach above
can't reach them. Static CSS, safe for Phoenix.HTML.raw/1.
@spec max_events() :: pos_integer()
Per-day caps for the month grids, shared by BOTH calendars — the "+N more"
link / whole-day popup contract depends on the two grids agreeing on when
a day overflows. max_events/0 caps single-day chips, max_multiday/0
caps multi-day bar slots.
@spec max_multiday() :: pos_integer()
Persist one calendar-display field. An invalid field or value is ignored.
Booleans go through put_flag/2; week_start_day is core's setting and is
NOT writable from here.
Flip one boolean display flag ("show_weekends", "show_week_numbers",
"fixed_weeks"). Unknown fields are ignored.
@spec read() :: %{ pattern: String.t(), mode: String.t(), speed: float(), brightness_min: float(), brightness_max: float(), wave_step: float(), opacity: float(), late_marker: String.t(), week_start: 1..7, show_weekends: boolean(), show_week_numbers: boolean(), fixed_weeks: boolean(), max_events: pos_integer(), max_multiday: pos_integer() }
The full calendar-display config (validated, with safe defaults): the grid
appearance half (weekends / week numbers / fixed weeks / per-day caps, plus
the site-wide week_start_day core setting the grids must honor) and the
overdue/late-marker half. One batched, uncached query — this runs on every
Overview reload (each {:projects, …} broadcast), not just mount, and
direct reads keep the settings page's live demo fresh.
@spec reset() :: :ok
Restore every calendar-display setting to its default.
@spec task_events( [{map(), %{start: NaiveDateTime.t(), end: NaiveDateTime.t()}}], String.t() | nil, String.t(), keyword() ) :: {[PhoenixLiveCalendar.Event.t()], %{optional(String.t()) => map()}}
Builds the Tasks-mode event list (+ a per-task metadata map) from prepared
{item, span} pairs — each a PhoenixKitProjects.ScheduleLayout item with
its computed %{start: NaiveDateTime, end: NaiveDateTime} span. Pure: the
caller runs the schedule walk (ScheduleLayout.tree/1 per project) and
passes only the LEAF items (sub-project container assignments excluded —
their child tasks stand for themselves here).
Each task becomes an all-day event spanning its scheduled days in the
viewer's timezone (offset, e.g. "+3" — same local basis as the rest of
the Overview), colored by the OWNING project's identity color
(color_for/1), so tasks group visually by project across the month.
Exclusive ends ([start, end)): a span ending exactly at local midnight
doesn't occupy that day, and a zero-length span still shows as a one-day
chip.
Returns {events, meta} — meta keyed by assignment uuid with
:project_uuid / :project_name / :status / :late for the day popup's
rows, the overdue filter, and click navigation.
Options:
:now— a UTCNaiveDateTime; when given, a not-done task whose span ended before it is late: its chip/bar gets the late marker class and its meta entrylate: true. Without:nownothing is flagged (pure date math only).:late_class— the marker class for late chips/bars (defaults to the red inset ring; passlate_marker_class(read())to honor the configured marker).
@spec task_late?( map(), %{:end => NaiveDateTime.t(), optional(atom()) => term()}, NaiveDateTime.t() | nil ) :: boolean()
A task is late when it isn't done and its scheduled span ended before
now — the schedule says it should already have finished. The shared rule
behind both calendars' red rings, late badges, and Overdue-only filters
(a nil now flags nothing — pure date math only).