An interactive demonstration of stock management and parts availability. Explore alert callouts, stock health indicators, and job impact linkages.
Parts Inventory Laboratory
Monitor critical parts availability, order statuses, and verify job impacts due to shortages.
Parts & Inventory
Alert-centric view — action required items first
R-410A Refrigerant 25lb
SKU: REF-410A-25 • On hand: 0 • Reorder at: 8
40VA Transformer 24V
SKU: ELEC-XFMR-40VA • On hand: 1 • Reorder at: 10
3-Ton Scroll Compressor
SKU: COMP-SCROLL-3T • On hand: 0 • Reorder at: 3
16x20x1 HVAC Filter MERV-8
SKU: FILT-1620-M8 • On hand: 6 • Reorder at: 20
20x25x1 HVAC Filter MERV-11
SKU: FILT-2025-M11 • On hand: 4 • Reorder at: 15
2-Stage Thermostat 7-Day Programmable
SKU: THERM-2ST-7D • On hand: 3 • Reorder at: 8
1/2" Copper Elbow 90-Degree
SKU: PIPE-CU-EL90-0.5 • On hand: 14 • Reorder at: 25
Parts On Order (5)
| Part Name | Qty On Order | Expected Delivery | Status |
|---|---|---|---|
R-410A Refrigerant 25lb REF-410A-25 | 12 | 2026-04-01 | Critical |
3-Ton Scroll Compressor COMP-SCROLL-3T | 2 | 2026-04-04 | Critical |
16x20x1 HVAC Filter MERV-8 FILT-1620-M8 | 24 | 2026-03-30 | Low |
2-Stage Thermostat 7-Day Programmable THERM-2ST-7D | 10 | 2026-04-02 | Low |
R-22 Refrigerant 30lb (Reclaimed) REF-R22-30-R | 4 | 2026-04-07 | Adequate |
3 Jobs Blocked by Parts Shortage
(+3 using low-stock parts)
WO-4821 — Greenfield Office Park
Needs: R-410A Refrigerant 25lb
WO-4834 — Westside Medical Center
Needs: 3-Ton Scroll Compressor
WO-4851 — Harbor View Apartments
Needs: 40VA Transformer 24V
WO-4802 — Ridgeline Retail Center
Needs: 16x20x1 HVAC Filter MERV-8
WO-4815 — Sunridge Elementary School
Needs: 2-Stage Thermostat 7-Day Programmable
WO-4858 — Lakewood Corporate Campus
Needs: 1/2" Copper Elbow 90-Degree
Variant1_AlertCentric.tsx (Widget Implementation)
import {
Paper,
Group,
Stack,
Text,
Badge,
Table,
ScrollArea,
Alert,
ActionIcon,
Tooltip,
Divider,
ThemeIcon,
} from "@mantine/core";
import {
HiExclamationTriangle,
HiShoppingCart,
HiTruck,
HiWrenchScrewdriver,
} from "react-icons/hi2";
import type { InventoryData, Part } from "./types";
const STOCK_COLORS = {
critical: "#fa5252",
low: "#fd7e14",
adequate: "#40c057",
overstocked: "#228be6",
} as const;
interface Props {
data: InventoryData;
}
function StockLevelBadge({ level }: { level: Part["stockLevel"] }) {
const labels: Record<Part["stockLevel"], string> = {
critical: "Critical",
low: "Low",
adequate: "Adequate",
overstocked: "Overstocked",
};
return (
<Badge
size="xs"
style={{ backgroundColor: STOCK_COLORS[level], color: "#fff", textTransform: "uppercase" }}
>
{labels[level]}
</Badge>
);
}
export function Variant1_AlertCentric({ data }: Props) {
const criticalParts = data.parts.filter((p) => p.stockLevel === "critical");
const lowParts = data.parts.filter((p) => p.stockLevel === "low");
const onOrderParts = data.parts.filter((p) => p.quantityOnOrder > 0 && p.expectedDelivery);
const blockedJobs = data.affectedJobs.filter((j) => !j.partAvailable);
const atRiskJobs = data.affectedJobs.filter((j) => j.partAvailable);
const hasCritical = criticalParts.length > 0;
return (
<Paper
withBorder
shadow="sm"
radius="md"
p="md"
style={{ display: "flex", flexDirection: "column", gap: 0 }}
>
{/* Header */}
<Group justify="space-between" mb="md">
<Group gap="sm">
<ThemeIcon variant="light" color="red" size="md" radius="sm">
<HiWrenchScrewdriver size={16} />
</ThemeIcon>
<div>
<Text fw={700} size="md" lh={1.2}>
Parts & Inventory
</Text>
<Text size="xs" c="dimmed">
Alert-centric view — action required items first
</Text>
</div>
</Group>
{hasCritical && (
<Badge color="red" size="md" leftSection={<HiExclamationTriangle size={12} />}>
{criticalParts.length} Critical
</Badge>
)}
</Group>
<Divider mb="md" />
<Stack gap="sm">
{/* Critical Stock Alert */}
{criticalParts.length > 0 && (
<Alert
icon={<HiExclamationTriangle size={18} />}
title={`${criticalParts.length} item${criticalParts.length > 1 ? "s" : ""} at critical stock level`}
color="red"
radius="sm"
styles={{ title: { fontWeight: 700 } }}
>
<Stack gap={6} mt={6}>
{criticalParts.map((part) => (
<Group key={part.id} justify="space-between" wrap="nowrap">
<div style={{ minWidth: 0 }}>
<Text size="sm" fw={600} truncate>
{part.name}
</Text>
<Text size="xs" c="dimmed">
SKU: {part.sku} • On hand: {part.quantityOnHand} • Reorder at:{" "}
{part.reorderPoint}
</Text>
</div>
<Tooltip label="Place reorder" withArrow position="left">
<ActionIcon
variant="filled"
color="red"
size="sm"
aria-label={`Reorder ${part.name}`}
>
<HiShoppingCart size={13} />
</ActionIcon>
</Tooltip>
</Group>
))}
</Stack>
</Alert>
)}
{/* Low Stock Alert */}
{lowParts.length > 0 && (
<Alert
icon={<HiExclamationTriangle size={18} />}
title={`${lowParts.length} item${lowParts.length > 1 ? "s" : ""} running low`}
color="orange"
radius="sm"
styles={{ title: { fontWeight: 700 } }}
>
<Stack gap={6} mt={6}>
{lowParts.map((part) => (
<Group key={part.id} justify="space-between" wrap="nowrap">
<div style={{ minWidth: 0 }}>
<Text size="sm" fw={600} truncate>
{part.name}
</Text>
<Text size="xs" c="dimmed">
SKU: {part.sku} • On hand: {part.quantityOnHand} • Reorder at:{" "}
{part.reorderPoint}
</Text>
</div>
<Tooltip label="Place reorder" withArrow position="left">
<ActionIcon
variant="filled"
color="orange"
size="sm"
aria-label={`Reorder ${part.name}`}
>
<HiShoppingCart size={13} />
</ActionIcon>
</Tooltip>
</Group>
))}
</Stack>
</Alert>
)}
{/* Parts On Order */}
{onOrderParts.length > 0 && (
<div>
<Group gap="xs" mb="xs">
<HiTruck size={15} color="#868e96" />
<Text size="sm" fw={600} c="dimmed">
Parts On Order ({onOrderParts.length})
</Text>
</Group>
<ScrollArea>
<Table
striped
withTableBorder
withColumnBorders
highlightOnHover
style={{ fontSize: 12 }}
>
<Table.Thead>
<Table.Tr>
<Table.Th>Part Name</Table.Th>
<Table.Th style={{ textAlign: "center" }}>Qty On Order</Table.Th>
<Table.Th>Expected Delivery</Table.Th>
<Table.Th style={{ textAlign: "center" }}>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{onOrderParts.map((part) => (
<Table.Tr key={part.id}>
<Table.Td>
<Text size="xs" fw={500}>
{part.name}
</Text>
<Text size="xs" c="dimmed">
{part.sku}
</Text>
</Table.Td>
<Table.Td style={{ textAlign: "center" }}>
<Text size="xs">{part.quantityOnOrder}</Text>
</Table.Td>
<Table.Td>
<Text size="xs">{part.expectedDelivery}</Text>
</Table.Td>
<Table.Td style={{ textAlign: "center" }}>
<StockLevelBadge level={part.stockLevel} />
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</ScrollArea>
</div>
)}
{/* Affected Jobs */}
<div>
<Group gap="xs" mb="xs">
<HiExclamationTriangle
size={15}
color={blockedJobs.length > 0 ? "#fa5252" : "#868e96"}
/>
<Text size="sm" fw={600} c={blockedJobs.length > 0 ? "red" : "dimmed"}>
{blockedJobs.length} Job{blockedJobs.length !== 1 ? "s" : ""} Blocked by Parts
Shortage
</Text>
{atRiskJobs.length > 0 && (
<Text size="xs" c="dimmed">
(+{atRiskJobs.length} using low-stock parts)
</Text>
)}
</Group>
<Stack gap={4}>
{data.affectedJobs.map((job) => (
<Group
key={job.workOrderId}
justify="space-between"
wrap="nowrap"
p="xs"
style={{
borderRadius: 6,
backgroundColor: !job.partAvailable ? "#fff5f5" : "#fff9f0",
border: `1px solid ${!job.partAvailable ? "#ffc9c9" : "#ffd8a8"}`,
}}
>
<div style={{ minWidth: 0 }}>
<Group gap={6} wrap="nowrap">
{!job.partAvailable && (
<HiExclamationTriangle size={13} color="#fa5252" style={{ flexShrink: 0 }} />
)}
<Text size="xs" fw={600} truncate>
{job.workOrderId} — {job.customerName}
</Text>
</Group>
<Text size="xs" c="dimmed" ml={!job.partAvailable ? 19 : 0}>
Needs: {job.requiredPart}
</Text>
</div>
<Badge
size="xs"
color={!job.partAvailable ? "red" : "orange"}
variant="light"
style={{ flexShrink: 0 }}
>
{!job.partAvailable ? "Blocked" : "At Risk"}
</Badge>
</Group>
))}
</Stack>
</div>
</Stack>
</Paper>
);
}