# `MishkaBlogWeb.Live.Admin.Dashboard.Home.Index`
[🔗](https://github.com/mishka-group/mishka_blog/blob/v0.0.1/lib/mishka_blog_web/live/admin/dashboard/home/index.ex#L5)

The blog admin overview: five KPI cards, eight section cards, an editorial calendar and three
summary panels, every number read from the database by `MishkaBlog.Dashboard.Overview`.

Both reads run off the render in a `start_async/3` — between them they issue thirty-odd counts, so
the page paints its labels and links first and fills the numbers in when they land. Until then
every figure is an em dash: `kpi_cards/1`, `section_cards/1` and `month_rows/1` each have a `nil`
clause over the same shared metadata, which is why there is no second skeleton markup to keep in
step with the real one. The calendar is only read when its panel is first opened, and again on
each month change, so a visitor who never opens it never pays for it. A calendar result is only
assigned when its month is still the selected one, so paging quickly cannot land an older read on
top of a newer one.

Everything from `kpi_cards/1` down is presentation: `delta/2`, `growth/2` and `percent/2` turn raw
counts into the labels the design shows, and every number, date and time is written in the reader's
language by `MishkaCmsCore.Localized`. `MishkaCmsCoreWeb.Live.Admin.Components.ActivityFeed`
supplies the "Logs" panel and the Calendar toggle rides in its `:toggles` slot, so both controls
sit on one row as the design has them.

## What each viewer is shown

The blog has two areas (`MishkaBlog.Dashboard.Overview.areas/1`): a holder of `admin:dashboard:blog:post` is
shown the posts, bloggers, collections, tags, teams, auto posting and views — the calendar and the
Logs panel included — and a holder of `admin:dashboard:blog:comment` the comments. The Engagement
figures divide comments by views, so they need both. A card, a row or a link whose area the
viewer does not hold is not drawn, and `MishkaBlog.Dashboard.Overview` does not read its data.

A figure counted from the view log (`untracked?/1`) carries a "Not tracked yet" label, and the
Auto Posting and Analytics cards (`on_roadmap?/1`) an "On our roadmap" one, both drawn by
`roadmap_badge/1`: nothing records a view yet, and those two sections are not built.

Setting the blog up, the Doctor and the kit upgrade write pages, components and a snippet, and
read the site they are for, so they need all three of `admin:dashboard:runtime:page` (the pages
and the page builder's snippet), `admin:dashboard:runtime:component` and
`admin:dashboard:runtime:site`.

See `MishkaBlog.Dashboard.Overview` for the queries.

# `activity_label`

```elixir
@spec activity_label(atom(), atom() | nil) :: String.t()
```

What a recent-activity entry says happened, worded from the record's own status.

A post is announced by the state it is actually in — the feed is ordered by creation, so the newest
post is usually a draft, and calling that "published" would tell every admin unfinished copy went
live.

# `activity_tone`

```elixir
@spec activity_tone(atom()) :: String.t()
```

Dot colour for a recent-activity entry, one per kind.

# `ago`

```elixir
@spec ago(DateTime.t(), DateTime.t()) :: String.t()
```

How long before `now` `at` was, e.g. `"3 minutes ago"` or `"yesterday"`; a moment after `now`
reads as `"now"`.

# `cell_tone`

```elixir
@spec cell_tone(map(), Date.t()) :: String.t()
```

Fill for one calendar square: outside the month, selected, today, or an ordinary day.

# `day_count_label`

```elixir
@spec day_count_label(non_neg_integer()) :: String.t()
```

The calendar sidebar's count line, e.g. `"1 post on this day"`.

# `day_label`

```elixir
@spec day_label(Date.t()) :: String.t()
```

Weekday, month and day for the calendar sidebar, e.g. `"Mon, Jul 20"`.

# `delta`

```elixir
@spec delta(integer(), integer()) :: String.t()
```

The `+2 from yesterday` line under a day counter, worded for every sign including zero.

# `growth`

```elixir
@spec growth(integer(), integer()) :: String.t()
```

The move between two windows as a percentage, or a word when there is nothing to compare to.

# `holds?`

```elixir
@spec holds?(MishkaBlog.Dashboard.Overview.areas(), [:post | :comment]) :: boolean()
```

Whether `areas` holds every blog area in `needs`.

# `kpi_cards`

```elixir
@spec kpi_cards(map() | nil, MishkaBlog.Dashboard.Overview.areas()) :: [
  {String.t(), String.t(), String.t(), String.t(), String.t(), String.t()}
]
```

The KPI cards `areas` may see, as `{label, icon, icon_tone, value, note, note_tone}` — of the
five, those whose blog areas `areas` (`MishkaBlog.Dashboard.Overview.areas/1`) holds.

The `nil` clause is what the page renders until `MishkaBlog.Dashboard.Overview.load/1` returns.

# `month_label`

```elixir
@spec month_label(Date.t()) :: String.t()
```

Month and year for the calendar header, e.g. `"July 2026"`.

# `month_rows`

```elixir
@spec month_rows(map() | nil, MishkaBlog.Dashboard.Overview.areas()) :: [
  {String.t(), String.t(), String.t()}
]
```

The rows of the This Month panel `areas` may see, as `{label, value, value_tone}`.

# `move_tone`

```elixir
@spec move_tone(number(), number()) :: String.t()
```

Tone for a figure that moved: green when it rose, red when it fell, muted when it held.

# `on_roadmap?`

```elixir
@spec on_roadmap?(String.t()) :: boolean()
```

Whether `title`, as a section card shows it, is a section that is on the roadmap.

# `percent`

```elixir
@spec percent(number(), keyword()) :: String.t()
```

`points` per hundred as the reader's language writes a percentage, e.g. `"12%"`.

# `post_time`

```elixir
@spec post_time(map()) :: String.t()
```

Time of day a post went out, or is due to — the calendar sidebar's second line.

# `posts_on`

```elixir
@spec posts_on(map() | nil, Date.t()) :: list()
```

The posts sitting on `date` in an already-loaded calendar.

# `quick_actions`

```elixir
@spec quick_actions(MishkaBlog.Dashboard.Overview.areas()) :: [
  {String.t(), String.t(), String.t(), boolean()}
]
```

The Quick Actions rows `areas` may follow, as `{label, icon, path, primary?}`.

# `render`

# `roadmap_badge`

The label drawn on a figure or a section that is not working yet.

`kind` is `:untracked` for a figure the view log feeds, `:roadmap` for a section on the roadmap.

## Attributes

* `kind` (`:atom`) (required) - Must be one of `:untracked`, or `:roadmap`.

# `section_cards`

```elixir
@spec section_cards(map() | nil, MishkaBlog.Dashboard.Overview.areas()) :: [
  {String.t(), String.t(), String.t(), String.t(), String.t(),
   [{String.t(), String.t(), String.t()}]}
]
```

The destination cards `areas` may open, as `{title, description, link, icon, icon_tone, rows}`,
where each row is `{label, value, value_tone}` — of the eight, those whose blog areas `areas`
holds.

# `status_label`

```elixir
@spec status_label(atom()) :: String.t()
```

The word a post status is shown as.

# `status_tone`

```elixir
@spec status_tone(atom()) :: String.t()
```

Chip colours for a post status, shared by the calendar grid and its sidebar.

# `untracked?`

```elixir
@spec untracked?(String.t()) :: boolean()
```

Whether `label`, as a KPI tile or a This Month row shows it, is a figure counted from the view log.

Nothing records a view yet, so the figure is empty or sample data and is drawn with a
`roadmap_badge/1`.

# `weekday_labels`

```elixir
@spec weekday_labels() :: [String.t()]
```

The calendar's column heads, Sunday first, e.g. `["Sun", "Mon", …]`.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
