Skip to content

Commit 302b910

Browse files
committed
edited dashbaord and forecasts page
1 parent 459ef22 commit 302b910

4 files changed

Lines changed: 102 additions & 6 deletions

File tree

backend/db.sqlite3

0 Bytes
Binary file not shown.

frontend/src/pages/DashboardPage.jsx

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ import {
1111
import '../styles/components.css'
1212
import '../styles/pages.css'
1313

14+
function formatDaysOfCover(value) {
15+
if (!Number.isFinite(value)) {
16+
return 'N/A'
17+
}
18+
if (value >= 999) {
19+
return '999+ days'
20+
}
21+
return `${value.toFixed(1)} days`
22+
}
23+
1424
function DashboardPage() {
1525
const { accessToken } = useAuth()
1626

@@ -96,6 +106,30 @@ function DashboardPage() {
96106
]
97107
}, [stockOverview, lowStockAlerts, consumptionSummary, supplierSummary, consumptionDays])
98108

109+
const consumptionByItemId = useMemo(() => {
110+
const map = new Map()
111+
consumptionSummary.forEach((row) => {
112+
map.set(String(row.item_id), Number(row.avg_daily_consumption || 0))
113+
})
114+
return map
115+
}, [consumptionSummary])
116+
117+
const actionItems = useMemo(() => {
118+
const sortedAlerts = [...lowStockAlerts].sort((a, b) => Number(b.shortage || 0) - Number(a.shortage || 0))
119+
return sortedAlerts.slice(0, 4).map((alert) => {
120+
const stock = stockOverview.find((row) => String(row.item_id) === String(alert.item_id))
121+
const avgDaily = consumptionByItemId.get(String(alert.item_id)) || 0
122+
const qtyAvailable = Number(stock?.quantity_available || 0)
123+
const daysOfCover = avgDaily > 0 ? qtyAvailable / avgDaily : Number.POSITIVE_INFINITY
124+
125+
return {
126+
...alert,
127+
daysOfCover,
128+
avgDaily,
129+
}
130+
})
131+
}, [lowStockAlerts, stockOverview, consumptionByItemId])
132+
99133
if (loading) {
100134
return <section className="page-stack"><p className="page-state">Loading dashboard data...</p></section>
101135
}
@@ -107,8 +141,8 @@ function DashboardPage() {
107141
return (
108142
<section className="page-stack">
109143
<PageHeader
110-
title="Operations Dashboard"
111-
subtitle="Historical and current view: stock levels, consumption trends, and reorder risk."
144+
title="What Needs Action Today?"
145+
subtitle="Historical and current view of stock, usage trends, and immediate reorder risks."
112146
/>
113147

114148
<div className="stats-grid">
@@ -122,6 +156,29 @@ function DashboardPage() {
122156
))}
123157
</div>
124158

159+
<section className="content-card">
160+
<h3>Priority Action Queue</h3>
161+
<p className="section-subtitle">Most urgent items to reorder based on shortage and current cover.</p>
162+
{actionItems.length === 0 ? (
163+
<p className="page-state">No urgent reorder action right now.</p>
164+
) : (
165+
<div className="action-grid">
166+
{actionItems.map((item) => (
167+
<article key={item.item_id} className="action-card">
168+
<div className="action-head">
169+
<p className="action-item">{item.item_name}</p>
170+
<span className="status-pill status-critical">Order now</span>
171+
</div>
172+
<p>Shortage: <strong>{item.shortage}</strong></p>
173+
<p>Avg daily usage: <strong>{item.avgDaily.toFixed(2)}</strong></p>
174+
<p>Days of cover: <strong>{formatDaysOfCover(item.daysOfCover)}</strong></p>
175+
<p>Supplier: <strong>{item.supplier_name || 'N/A'}</strong></p>
176+
</article>
177+
))}
178+
</div>
179+
)}
180+
</section>
181+
125182
<section className="content-card">
126183
<h3>Current Stock Levels</h3>
127184
<p className="section-subtitle">What has happened now: quantity available vs reorder thresholds.</p>
@@ -132,25 +189,35 @@ function DashboardPage() {
132189
<th>Item</th>
133190
<th>Category</th>
134191
<th>Available</th>
192+
<th>Days of Cover</th>
135193
<th>Reorder</th>
136194
<th>Safety</th>
137195
<th>Status</th>
138196
</tr>
139197
</thead>
140198
<tbody>
141199
{stockOverview.map((row) => (
142-
<tr key={row.item_id}>
200+
(() => {
201+
const avgDaily = consumptionByItemId.get(String(row.item_id)) || 0
202+
const daysOfCover = avgDaily > 0
203+
? Number(row.quantity_available || 0) / avgDaily
204+
: Number.POSITIVE_INFINITY
205+
return (
206+
<tr key={row.item_id}>
143207
<td>{row.item_name}</td>
144208
<td>{row.category}</td>
145209
<td>{row.quantity_available}</td>
210+
<td>{formatDaysOfCover(daysOfCover)}</td>
146211
<td>{row.reorder_level}</td>
147212
<td>{row.safety_stock}</td>
148213
<td>
149214
<span className={`status-pill status-${row.status?.toLowerCase()}`}>
150215
{row.status}
151216
</span>
152217
</td>
153-
</tr>
218+
</tr>
219+
)
220+
})()
154221
))}
155222
</tbody>
156223
</table>

frontend/src/pages/ForecastsPage.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,8 @@ function ForecastsPage() {
180180
return (
181181
<section className="page-stack">
182182
<PageHeader
183-
title="Forecasting Workspace"
184-
subtitle="Future view: 30-day demand predictions, model comparison, and suggested order planning."
183+
title="What Should We Order Next?"
184+
subtitle="Future view with selected horizon, model confidence, and recommended order quantity."
185185
/>
186186

187187
<section className="content-card compact-card">

frontend/src/styles/pages.css

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,35 @@
8383
color: #1b6f3d;
8484
}
8585

86+
.action-grid {
87+
margin-top: 0.65rem;
88+
display: grid;
89+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
90+
gap: 0.75rem;
91+
}
92+
93+
.action-card {
94+
border: 1px solid #f2d2d2;
95+
background: #fffdfc;
96+
border-radius: 12px;
97+
padding: 0.8rem;
98+
display: grid;
99+
gap: 0.28rem;
100+
font-size: 0.9rem;
101+
}
102+
103+
.action-head {
104+
display: flex;
105+
justify-content: space-between;
106+
gap: 0.5rem;
107+
align-items: center;
108+
}
109+
110+
.action-item {
111+
font-weight: 700;
112+
color: #7f1d1d;
113+
}
114+
86115
.alert-grid {
87116
margin-top: 0.65rem;
88117
display: grid;

0 commit comments

Comments
 (0)