G Gaia

Switch factory

CYTO-11 — Balakong Jaya CTSB CYTO-29 CTSB CYTOWELL-54 — Taming Jaya PIC/S CTWSB CYTOCNGZ-602 — Guangzhou CTCNGZ

Only factories you are granted appear here (Filament tenant menu).

Leo Lim

leo@navi.my · Owner / Admin

Profile settings Change password

Theme

Sign out
RetailSellR&DMakeBuyStockPlanItemsGMPOpsAdminInsights

Announcements

One place to tell the right people — targeted by role and factory, read receipts included.

announcements
Title Audience Window Pinned Acks By Status

Project Gaia — static mockup in Filament v3 idiom. No backend. Seed rows are illustrative; ack counts are for demonstration only.

Announcements

New announcement

Not saved

plain text — rendered escaped, never HTML; write it in any language your team reads — BM, 中文, မြန်မာ

Leave empty = all roles (audience_roles null) — matched against the viewer's role at publish and dashboard render time.

Leave empty = all factories (audience_factories null) — role ∩ factory decides the final match.

Pinned
Require ack

Pinned sorts first on every dashboard banner; Require ack swaps the dashboard's dismiss for an explicit Acknowledge button (ack_required).

Leave blank for an ongoing / no-end-date notice.

Announcements — field manifest

FieldFilament componentTypeSourceFK target
announcements
TitleTextInput / TextColumnstringstored: announcements.title
BodyTextarea / TextColumn (escaped)textstored: announcements.body — user content, ALWAYS rendered escaped, never interpreted as HTML
Audience — rolesCheckboxList (chips) / TextColumn badgesjson nullstored: announcements.audience_roles — null = all rolesroles.id (each ticked)
Audience — factoriesCheckboxList (chips) / TextColumn badgesjson nullstored: announcements.audience_factories — null = all entitiesfactories.id (each ticked)
PinnedToggle / IconColumn::boolean()boolstored: announcements.pinned — pinned rows sort first on every dashboard banner
Ack requiredToggle / IconColumn::boolean()boolstored: announcements.ack_required — true swaps the dashboard's dismiss for an explicit Acknowledge action
Publish windowDatePicker (Starts/Ends) / TextColumndatetime nullstored: announcements.starts_at / announcements.ends_at — null ends_at = no expiry
StatusTextColumn badge (derived)enum Active/Scheduled/Expiredderived: now() vs starts_at/ends_at — not a stored column
AcksTextColumn (derived "x/y read")computedderived: count(announcement_reads WHERE announcement_id) ÷ matched-audience user countannouncement_reads.announcement_id
ByTextColumn (relation)relationstored: announcements.created_byusers.id
announcement_reads
AnnouncementBelongsTorelationstored: announcement_reads.announcement_idannouncements.id
UserBelongsTorelationstored: announcement_reads.user_idusers.id
Read atTextColumn::since()datetimestored: announcement_reads.read_at — written when a user dismisses the dashboard banner, or taps Acknowledge for an ack_required post

Engine / architecture notes

  • Authoring is Owner/Admin only. AnnouncementPolicy::create()/update() gate this Resource; every other role only ever reads announcements matching their own audience (server-side filter, not client).
  • Publish fans out ONE bell notification per matched user — role ∩ factory, per D36's NotificationRouter — not one row per announcement.
  • Display doctrine: a dismissible banner surfaces on every dashboard for announcements active for the viewer's role+factories, pinned first; the full history also lives in Notifications with an announce category.
  • Reads/acks: dismissing the banner (or tapping the explicit Acknowledge button when ack_required=true) writes one announcement_reads row (announcement_id, user_id, read_at) — the Acks column is that count over the matched-audience size, e.g. 12/18 read.
  • Body is always rendered escaped (Blade {{ }} / Filament's default column escaping) — user-authored free text is never interpreted as HTML, matching the never-render-as-HTML rule in SECURITY §3.
  • Locale note: the chrome around this form is translated (D47 — en/ms/zh/my), but the title/body itself is written by the author in whatever language their audience reads — see the multilingual PPE example on the operator dashboard.