PhoenixKitCalendar.Web.WidgetSupport (PhoenixKitCalendar v0.2.2)

Copy Markdown View Source

Shared, defensive helpers for the calendar's dashboard widgets.

Every function here is scoped to the widget VIEWER (via the scope the host dashboard passes) and fails soft: no scope, a disabled module, or a missing table all collapse to an empty result rather than crashing the host — a widget must never take the dashboard down with it.

Times are handled the same way as the rest of the calendar: events are stored in UTC and shown in the viewer's timezone (their user_timezone setting → site time_zone"0"; an IANA id or a legacy fixed offset, always through core's per-instant helpers).

Summary

Functions

The viewer's non-cancelled events overlapping [from, until) (dates in the viewer's frame), through the authorized context path. "Theirs" means the same set the main calendar shows a person — events they OWN plus events they are a PARTICIPANT in — never anyone else's. Any failure (no scope, disabled module, missing table) yields [].

A scale-aware self-fit font-size style: the type grows with its cq slot but stays clamped to a consistent px range, so widgets look cohesive at any box size. The --pk-scale var (set by the dashboards fit hook) keeps the clamp proportional when the board renders scaled down.

The viewer's LOCAL today (their offset frame), not UTC today.

The list of calendar dates an event occupies in the viewer's frame — used to place per-day markers. All-day events use their exclusive DATE end; timed events use their shifted local dates, excluding an end that lands exactly on local midnight (that boundary belongs to the previous day).

Whether the event occupies date in the viewer's frame.

Chronological sort key IN THE VIEWER'S FRAME. An all-day event sorts at the very start of its day (00:00), so events read soonest-first across days AND all-day events lead a single day's agenda — one key serves both the Upcoming and Today widgets.

The viewer's timezone value — an IANA id or a legacy offset (never raises).

The viewer's user uuid, or nil when there is no authenticated scope.

Functions

fetch_events(scope, from, until)

@spec fetch_events(term(), Date.t(), Date.t()) :: [
  PhoenixKitCalendar.Schemas.Event.t()
]

The viewer's non-cancelled events overlapping [from, until) (dates in the viewer's frame), through the authorized context path. "Theirs" means the same set the main calendar shows a person — events they OWN plus events they are a PARTICIPANT in — never anyone else's. Any failure (no scope, disabled module, missing table) yields [].

fit_text(min_px, cq, max_px)

@spec fit_text(number(), String.t(), number()) :: String.t()

A scale-aware self-fit font-size style: the type grows with its cq slot but stays clamped to a consistent px range, so widgets look cohesive at any box size. The --pk-scale var (set by the dashboards fit hook) keeps the clamp proportional when the board renders scaled down.

local_today(scope)

@spec local_today(term()) :: Date.t()

The viewer's LOCAL today (their offset frame), not UTC today.

occupied_dates(event, tz)

@spec occupied_dates(PhoenixKitCalendar.Schemas.Event.t(), String.t()) :: [Date.t()]

The list of calendar dates an event occupies in the viewer's frame — used to place per-day markers. All-day events use their exclusive DATE end; timed events use their shifted local dates, excluding an end that lands exactly on local midnight (that boundary belongs to the previous day).

on_date?(event, date, tz)

Whether the event occupies date in the viewer's frame.

sort_key(event, tz)

Chronological sort key IN THE VIEWER'S FRAME. An all-day event sorts at the very start of its day (00:00), so events read soonest-first across days AND all-day events lead a single day's agenda — one key serves both the Upcoming and Today widgets.

Both kinds must answer in the SAME frame or the ordering breaks: an all-day starts_on is a LOCAL calendar date, while starts_at is a true UTC instant. Compared directly, an early-morning timed event sorted ahead of its own day's all-day events for every viewer east of UTC — 02:00 in Tallinn is 23:00Z the day before — which put it under the previous day in Upcoming and above the all-day rows the Today widget promises to lead with. The timed key is therefore the viewer's wall clock, labelled UTC so the two compare.

viewer_tz(scope)

@spec viewer_tz(term()) :: String.t()

The viewer's timezone value — an IANA id or a legacy offset (never raises).

viewer_uuid(scope)

@spec viewer_uuid(term()) :: binary() | nil

The viewer's user uuid, or nil when there is no authenticated scope.