Repository navigation
feat: plant overview dashboard sections, review fixes and work-order permissions - #381
Merged
Merged
Conversation
The sidebar alert badge, its Latest alerts panel, the Alerts page and the work orders ?overdue=1 filter took "today" from toISOString(), the UTC date. Outside UTC that moved the overdue boundary hours away from the plant's midnight and made the live badge disagree with the server's nav.alertCount (AlertController::totalCount uses plant-zone today()). - lib/i18n: todayInTimezone() and timezone() getter - lib/alerts: one shared overdue/count rule mirroring totalCount - lib/usePlantToday: re-checks the date every minute so the badge flips at the plant's midnight without a data change - tests/fixtures/alert-count-cases.json feeds both the vitest and the Laravel parity tests Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…y targets card - ProductionTargets::plannedMinutesByLine() only weighs scheduled operations of active orders, so finishing, cancelling or rejecting a planned order no longer keeps its line's weight in the plant OEE target. - FirstPassYieldService finds each batch's first check with SQL aggregates (MIN(checked_at) per batch, lowest id on ties) instead of loading every check; results are unchanged. - Settings → System sends the Production targets prop only for the Production tab (Inertia::optional otherwise); the card fetches it with a partial reload when the tab opens and shows "Loading…" meanwhile. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The admin work-order create/store and edit/update routes were gated only
by the Orders tab, and the supervisor edit/update only by the role, so
WorkOrderPolicy::create/update were never consulted there.
- StoreWorkOrderRequest / both UpdateWorkOrderRequests authorize via the
policy, so the 403 lands before validation (no order_no probing)
- admin create/edit and supervisor edit call $this->authorize()
- share auth.user.can.{createWorkOrders,editWorkOrders} with Inertia and
hide New Work Order (lists, planner, weekly schedule) and Edit without them
- list pages no longer send create-form options to users who can't create
Seeder unchanged: Admin and Supervisor keep create/edit; an Operator given
tab:orders loses create/edit on /admin/work-orders (deliberate).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…silent collectors
- Header time left: "2 h 59 min left" under three hours, whole hours rounded
up above (was truncated, so 179 min read "2 h left").
- Every ingested machine signal bumps the open workstation_states row's
updated_at (throttled to once a minute, query-builder update, so no
broadcast). A line whose machine-reported states have not been refreshed
for more than 10 minutes now reads `unknown` ("No data") instead of its last
state; manual states and person-reported facts still apply.
- Open stops are read once and passed to ShiftMonitorService::currentStates(),
which now keeps every open stop per station: since = the oldest, reason = the
oldest given cause, needs_reason while any is a placeholder.
- MAINTENANCE and WAITING set needs_reason like FAULT/STOPPED; AUTO-* reason
names are never shown as a cause.
- New Lines section (state pill, since, reason) with a legend including
"No data"; shared line-state vocabulary in lineStates.js.
- useOverviewRefresh tells its own reload apart from user visits by the visit
params and counts overlapping visits, matching its comment.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # backend/lang/en.json # backend/lang/pl.json
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The operator layout restyle dropped the sidebar that rendered auth.user.accessibleTabLinks, so an Operator granted a tab in Settings → Access (e.g. Orders) had no way to click through to it. Bring it back with the current design tokens: "Lines" first, then each granted tab. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Overdue orders panel now states the cut-off it uses ("Due before
24/10/2026 (Europe/Warsaw)"), so the plant-timezone day boundary is
visible rather than implied. The Latest alerts panel no longer shows a
bare "No open issues." under a non-zero count: it says the alerts are
overdue or blocked work orders.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
First plant-overview section, "Line activity · this shift": one bar per active line across the current plant shift (Running / Idle / Blocked / Down up to a Now marker), hover tooltips with times, duration and stop reason, per-line uptime, and the line name linking to its Shift Overview for users with the shift_monitor tab. PlantOverviewService::lineTimeline() aggregates per minute: every signal on the line casts a candidate and the worst wins (down > blocked > running > idle), reusing the line-state mapping now shared with lineStates(). Station slices come from the new ShiftMonitorService::stateTimelines() (no counter feed); lines without a collector fall back to batches, in-progress orders and manual stops. Fixed query count regardless of line count. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`distinct` on the nested `grants.*.*` wildcard compares values across roles, so the form - which posts every role's grants, and Admin and Supervisor both hold both inspection grants on a fresh install - always failed validation, and the page showed no error. Drop the rule (the controller already de-duplicates) and render validation errors above Save. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The hatched background ran under the label, so "No data" was hard to read on white. The pill now has a plain card background with a dashed edge, and the hatch moves to a small swatch in front of the label. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With no open issues the Latest alerts panel now reads "No open issues. The alerts are work orders: 1 overdue." instead of pointing at a list that isn't there. Clicking the Alerts nav item closes the hover panel and keeps it closed until the pointer leaves, so it no longer covers the Alerts page it just opened. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Plant OEE since shift start with the gap to target and the biggest loss, availability / performance / quality bars with a target tick, and every active line's OEE sorted best first, coloured against the line's own target (planning efficiency, default 85%). An (i) tooltip explains the formula and where the target comes from. Data is the shift monitor's live machine data, not oee_records: ShiftMonitorService::oeeInputs() exposes the raw per-station sums and PlantOverviewService::shiftOee() combines them — availability weighted by opportunity minutes, performance by expected pieces (capped per station), quality by pieces made — so a single-station line matches its Plant Board tile exactly. Query count is fixed regardless of line and station count. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A collection snapshot sends work_orders.due_date as the raw column (plant
wall time, "2026-06-15 00:00:00"), but a live delta sends the model's
datetime cast serialised as UTC ISO ("2026-06-14T22:00:00.000000Z" for
the same value in Warsaw). Slicing the first 10 characters of the delta
form gave the previous day, so an order due today counted as overdue as
soon as it was updated.
- lib/i18n: plantDate() — zone-less values kept as-is, zoned ones
converted into the plant timezone
- lib/alerts: isOverdue/countAlerts read due dates through plantDate
- fixture gains broadcast_due_date; the server test asserts it equals
attributesToArray(), the front counts both forms
- work-orders/Index imports TERMINAL_WO_STATUSES instead of a copy
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- work-order lists tell a user without `create work orders` why there is no New Work Order button - the admin/supervisor detail pages hide Edit without `edit work orders`; the admin show page no longer serves the edit-form options to them - Settings → Tab Access lists, read-only, which roles hold `create work orders` / `edit work orders` - synced lists re-read their snapshot after any completed Inertia write, so a just-created order or account shows even when the WebSocket is down Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Current-shift downtime grouped by reason, next to OEE: total, lines hit, loss as a share of planned time so far, reason rows with line codes and kind-coloured bars, unclassified stops linking to Shift Monitor, and a "Downtime report" link to Plant Board. Data comes from the new PlantOverviewService::downtime(), delivered as overview.downtime so the 60 s partial reload refreshes it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…om collectors - A stop a person logged or classified on a station whose collector went silent now still makes the line Down with its reason, instead of being hidden behind "No data". Only the machine's own untouched AUTO-* stop is dropped with the stale state. - WorkstationStateMachine::transition() no longer marks a signal, so an operator re-setting the same state cannot make a silent line look live; the ingestor's call is the only one. - markSignal() remembers per instance when it last ran per station, so a long-running poller issues the UPDATE once a minute, not once per pulse. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Four "Shift at a glance" tiles next to the production chart row: - Orders not scheduled: the planner backlog, through a new shared WorkOrder::plannerBacklog() scope that SchedulePlannerService::board() now uses too, with due-this-week / overdue in the plant timezone - Lines producing: running / active lines from the header's line states - Scrap rate: ScrapReportService maths over the current ShiftWindow, limited to the tenant's active lines, against the D2 target - First-pass yield: FirstPassYieldService for the shift, "—" when the quality module is off or nothing was checked Tone and links are resolved server-side (ShiftKpiService); a tile links only to a tab the user can open. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two plant-overview sections fed by a new `production` key in the dashboard's `overview` prop (ShiftPlanService::forShift): - plan: scheduled orders' planned_qty spread linearly over their planned_start_at/planned_end_at window, clipped to the shift hours — the same window the Schedule board draws (an applied simulation writes its envelope back onto it; draft simulations are never read); - actual: a new append-only ledger, work_order_output_entries, written by WorkOrderOutputObserver on every produced_qty change, whichever path made it; - running totals, pace now, plan pcs/h and a factual summary (below/ above plan since, overlapping unplanned downtime). No plan is null. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…erview - The OPC UA gateway posts readings only on change, so a press stuck in FAULT went quiet while its gateway kept heart-beating, and the line turned to "No data" after 10 minutes. The heartbeat endpoint now marks every station its connection's active tags read as heard from (one throttled query-builder update, no model events). - An operator's note on an unchanged state no longer bumps updated_at, so it cannot make a silent machine look live. - A stop logged by hand on a fresh station whose state carries no stops (RUNNING, IDLE, SETUP, CLEANING) now reaches the line state, e.g. a quality hold on a running machine. - ShiftMonitorDemoSeeder documents that its lines read "No data" ten minutes after seeding unless a collector reports on them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The "% of planned time" summed every station's stop against one line's time, so four stations faulting together read 400 %. Overlapping stops are now merged per line and planned stops taken out of both loss and base, so it stays within 100 %. The total and reason rows still list every stop, matching Plant Board. Plant Board's downtime total is cached per tenant for a minute, the poll lag for line-level stops is documented, and durationLabel moves to lib/i18n so production pages don't import a dashboard component. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Plant overview gains a "Lines" section — one card per active line with its state pill (coloured edge for blocked/down), the current order's progress (produced / planned · product code, +N more), and either the ETA from the last hour's output (falling back to planned_end_at, "Late" past it) or the state reason with its duration. Cards open the line's Shift Overview. A summary strip shows work orders today (plant day), materials below minimum, open non-conformances (new Issue::nonConformance scope, not every open issue) and the next order; fields of disabled modules are dropped and links are gated by tab access. Data comes from two new services (LineProgressService, PlantSummaryService) added to PlantOverviewService::overview() as `line_orders` and `summary`, so the existing 60 s partial reload refreshes them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s from - ShiftMonitorDemoSeeder mirrors each line's output (its last station's batches) into work_order_output_entries in quarter-hour steps, so the example company's dashboard shows actual output instead of 0; re-runs replace their own rows. - The planned-vs-actual card says how many scheduled orders make up the shift plan, with an "Open schedule" link for users with the schedule tab. - The cumulative chart is shorter on phones so the card stack stays compact. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The card listed every classified reason first and Unclassified last, so a 0 m changeover sat above a 3 h unclassified block and the list read as unsorted. Unclassified now takes its place by time (after equal ones). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The plant-overview page brings its own padding (32/40 px as in the mockup, tighter on phones); AppLayout's <main> has none, so the heading, button and cards touched the window edges. - Output per hour fits a phone: narrower columns, "5.3k" value labels and hour-only time labels when the columns are too narrow for "5,318" and "22:00". - Shared useWidth hook (callback ref) for both charts. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The example company's output ledger was a copy of the shift-monitor batches, which run at nameplate rate on whatever orders a line has: next to the shift plan the dashboard read 150-200 % of plan within an hour. ShiftMonitorDemoSeeder now records, for every order scheduled in each line's live shift, 72-98 % of its planned pieces per hour in quarter-hour steps up to now; lines with nothing scheduled get nothing. App logic is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md # backend/lang/en.json # backend/lang/pl.json
# Conflicts: # CHANGELOG.md # backend/app/Services/Dashboard/PlantOverviewService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # backend/app/Services/Dashboard/PlantOverviewService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # backend/app/Services/Dashboard/PlantOverviewService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # CHANGELOG.md # backend/app/Services/Dashboard/PlantOverviewService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # CHANGELOG.md # backend/app/Http/Controllers/Web/Admin/DashboardController.php # backend/app/Services/Dashboard/PlantOverviewService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # CHANGELOG.md # backend/app/Http/Controllers/Web/Admin/DashboardController.php # backend/app/Services/Dashboard/PlantOverviewService.php # backend/app/Services/Production/ShiftMonitorService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # CHANGELOG.md # backend/app/Services/Dashboard/PlantOverviewService.php # backend/lang/en.json # backend/lang/pl.json # backend/resources/js/components/dashboard/overview/sections.js
# Conflicts: # CHANGELOG.md # backend/lang/en.json # backend/lang/pl.json
Work-order create/edit permission on web forms (task G2), with its operator-sidebar regression fix and the Settings → Access quality-grant fix. Left out on purpose: the realtimeCollection.js change that re-reads every live collection after any non-GET Inertia visit. It is an app-wide behaviour change unrelated to permissions; tracked as a separate follow-up.
The temporary Lines section from the review fixes was replaced by the line cards, so the spec looks for data-testid="line-card" instead of "overview-line". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…the create permission With no shifts set up, the overview sections report on the fallback 06–18 / 18–06 window while the header said "No shift running". The header now shows that window, labelled as the default, so the two agree. The "New work order" button now also requires the create work orders permission (auth.user.can.createWorkOrders), matching the form, which answers 403 without it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…gration # Conflicts: # CHANGELOG.md # backend/lang/en.json # backend/lang/pl.json
Drops a fragment the merge left attached to the line-cards entry, updates the header entry for the line cards and the default shift window, and removes the entry for the live-sync re-read that was left out of this branch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one): 1
Please check the settings in the CodeRabbit UI or the
|
en-GB formats 5318 as "5.3K" or "5.3k" depending on the CLDR data the Node runtime ships (CI runs Node 20, which gives "5.3k"). The test now checks the shortening, not the letter case. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
jakub-przepiora
marked this pull request as ready for review
October 25, 2026 14:20
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Completes the Plant overview admin dashboard from the design mockup, on top of the foundation (#379) and the production targets (#380) already in
develop. Admin → Dashboard now has every section of the mockup, fed by live data:Two fixes outside the dashboard, found while analysing it:
WorkOrderPolicy. Having the Orders tab was enough before. The pages now return 403 without the permission and the buttons are hidden.Why
The admin dashboard had no view of the shift in progress. The mockup asked for one screen showing what each line is doing, how far production is from plan, and what needs attention.
How it was built
Each section was built as its own task, and the tasks were then integrated here:
PlantOverviewService::machineCandidate()), shared by the current state, the timeline, the cards and Needs action.OverviewCardhas its own module. Sections took it fromOverviewGrid, which imports every section.lineStates.jsfor labels and colours.GET /api/v1/admin/dashboard(used by the mobile app), the shared synced collections,OeeCalculationServiceand its cache key,OeeGauge,dashboard_widgetsand theWidgetRegistrycontract.Please review
work_order_output_entries, written byWorkOrderOutputObserveron every change toproduced_qty. It is the source of output per hour. Allproduced_qtywrites inapp/go through the model, so none bypass it. Hourly figures start from the upgrade.realtimeCollection.jsthat re-read every live collection after any form submit. It is app-wide and unrelated to this work, so it is a separate follow-up.How it was verified
php artisan test: 3715 passed (1 skipped, 1 risky, as ondevelop).npx vitest run, 251 passed.vite buildpasses,pint --testis clean on the changed files, anden.json/pl.jsonhave the same key set.Known follow-ups (not in this PR)
ADMIN_*env (web-installer and manual installs). With the Docker compose defaults it recreates the admin with a known password.🤖 Generated with Claude Code