fix inventory
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link, useSearchParams } from 'react-router-dom'
|
||||
import { ApiHttpError } from '../../api/http'
|
||||
import { fetchInventoryDashboard } from '../../api/inventory'
|
||||
import { INVENTORY_BOOK_BASE } from './inventoryPaths'
|
||||
|
||||
/**
|
||||
* Inventory Dashboard — summary counts by type, low stock, out of stock, etc.
|
||||
* Backend: GET /api/v1/administrator/inventory/dashboard
|
||||
*/
|
||||
export function InventoryDashboardPage() {
|
||||
const [searchParams] = useSearchParams()
|
||||
const [data, setData] = useState<Record<string, unknown>>({})
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const load = useCallback(() => {
|
||||
setLoading(true)
|
||||
fetchInventoryDashboard(searchParams)
|
||||
.then((res) => {
|
||||
if (res && typeof res === 'object') {
|
||||
setData(res as Record<string, unknown>)
|
||||
}
|
||||
setError(null)
|
||||
})
|
||||
.catch((e: unknown) => {
|
||||
setError(e instanceof ApiHttpError ? e.message : 'Unable to load dashboard.')
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [searchParams])
|
||||
|
||||
useEffect(() => {
|
||||
load()
|
||||
}, [load])
|
||||
|
||||
const byType = (data.by_type as Record<string, number>) ?? {}
|
||||
const totalItems = Number(data.total_items ?? 0)
|
||||
const lowStockCount = Number(data.low_stock_count ?? 0)
|
||||
const outOfStockCount = Number(data.out_of_stock_count ?? 0)
|
||||
const needsRepairCount = Number(data.needs_repair_count ?? 0)
|
||||
const missingCount = Number(data.missing_count ?? 0)
|
||||
|
||||
return (
|
||||
<div className="container-fluid px-0 mt-3">
|
||||
<div className="d-flex justify-content-between align-items-center mb-3 px-3 flex-wrap gap-2">
|
||||
<h3 className="mb-0">Inventory Dashboard</h3>
|
||||
<div className="d-flex gap-2">
|
||||
<Link className="btn btn-outline-info btn-sm" to="/app/administrator/inventory/stock-status">
|
||||
Stock Status
|
||||
</Link>
|
||||
<Link className="btn btn-outline-secondary" to={INVENTORY_BOOK_BASE}>
|
||||
Back to Items
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error ? <div className="alert alert-danger mx-3">{error}</div> : null}
|
||||
|
||||
{loading ? (
|
||||
<p className="text-muted px-3">Loading…</p>
|
||||
) : (
|
||||
<div className="row g-3 px-3">
|
||||
{/* Summary Cards */}
|
||||
<div className="col-sm-6 col-lg-4">
|
||||
<div className="card border-primary h-100">
|
||||
<div className="card-body text-center">
|
||||
<h5 className="card-title text-primary">{totalItems}</h5>
|
||||
<p className="card-text">Total Items</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-sm-6 col-lg-4">
|
||||
<div className="card border-warning h-100">
|
||||
<div className="card-body text-center">
|
||||
<h5 className="card-title text-warning">{lowStockCount}</h5>
|
||||
<p className="card-text">Low Stock Items</p>
|
||||
{lowStockCount > 0 ? (
|
||||
<Link className="btn btn-sm btn-warning" to="/app/administrator/inventory/low-stock">
|
||||
View Low Stock
|
||||
</Link>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-sm-6 col-lg-4">
|
||||
<div className="card border-danger h-100">
|
||||
<div className="card-body text-center">
|
||||
<h5 className="card-title text-danger">{outOfStockCount}</h5>
|
||||
<p className="card-text">Out of Stock</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-sm-6 col-lg-4">
|
||||
<div className="card border-info h-100">
|
||||
<div className="card-body text-center">
|
||||
<h5 className="card-title text-info">{needsRepairCount}</h5>
|
||||
<p className="card-text">Needs Repair</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-sm-6 col-lg-4">
|
||||
<div className="card border-secondary h-100">
|
||||
<div className="card-body text-center">
|
||||
<h5 className="card-title text-secondary">{missingCount}</h5>
|
||||
<p className="card-text">Missing / Cannot Find</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Items by Type */}
|
||||
<div className="col-12">
|
||||
<div className="card">
|
||||
<div className="card-header">Items by Type</div>
|
||||
<div className="card-body">
|
||||
{Object.keys(byType).length === 0 ? (
|
||||
<p className="text-muted mb-0">No items found.</p>
|
||||
) : (
|
||||
<div className="table-responsive">
|
||||
<table className="table table-striped align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th className="text-end">Count</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{Object.entries(byType).map(([type, count]) => (
|
||||
<tr key={type}>
|
||||
<td style={{ textTransform: 'capitalize' }}>{type}</td>
|
||||
<td className="text-end">{Number(count).toLocaleString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="col-12">
|
||||
<div className="card">
|
||||
<div className="card-header">Quick Actions</div>
|
||||
<div className="card-body">
|
||||
<div className="d-flex gap-2 flex-wrap">
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/classroom">
|
||||
Classroom Equipment
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to={INVENTORY_BOOK_BASE}>
|
||||
Books
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/office">
|
||||
Office Supplies
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/kitchen">
|
||||
Kitchen Supplies
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/movements">
|
||||
Movements
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/summary">
|
||||
Summary
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/reorder-suggestions">
|
||||
Reorder Suggestions
|
||||
</Link>
|
||||
<Link className="btn btn-outline-primary" to="/app/administrator/inventory/low-stock">
|
||||
Low Stock
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user