An interactive demonstration of work order transitions and pipeline status. Explore funnel conversions, drilldown status summaries, and ticket age heatmaps.
Work Order Pipeline Laboratory
Visualize the active pipeline using funnel charts, stat card drilldowns, and aging heatmaps.
Work Order Pipeline
Live status across all active orders
emergency
high
normal
low
Red border = overdue (>7d)
New
AC unit not cooling - Unit #3
WO-1001
Thermostat replacement - main floor
WO-1002
1d
Water heater not igniting
WO-1003
Annual HVAC maintenance inspection
WO-1004
2d
Pending Schedule
Panel upgrade 100A to 200A
WO-1005
3d
Drain line camera inspection
WO-1006
4d
Mini-split installation - master bedroom
WO-1007
5d
Scheduled
Furnace tune-up and filter change
WO-1008
2d
Bathroom faucet replacement x3
WO-1009
1d
GFCI outlet installation - kitchen
WO-1010
3d
In Progress
Commercial HVAC system install - Phase 1
WO-1011
4d
Sewer line repair - main cleanout
WO-1012
1d
Electrical panel inspection and labeling
WO-1013
2d
Awaiting Parts
Compressor replacement - rooftop unit
WO-1014
9d
Water softener system install
WO-1015
7d
EV charger rough-in - garage
WO-1016
10d
Overdue
Heat pump not heating - building B
WO-1019
15d
Gas line pressure test - new build
WO-1020
17d
Lighting retrofit - parking garage
WO-1021
16d
Condensate drain cleaning - server room
WO-1022
14d
On Hold
Ductwork sealing - basement zone
WO-1017
6d
Backflow preventer certification
WO-1018
5d
Completed
Toilet flapper and fill valve replacement
WO-1023
1d
Smoke detector + CO alarm install
WO-1024
AC filter replacement and coil clean
WO-1025
Boiler annual service - commercial
WO-1026
1d
Backyard hose bib installation
WO-1027
Variant1_FunnelPipeline.tsx (Widget Implementation)
import { Badge, Box, Group, Paper, ScrollArea, Stack, Text, Tooltip } from "@mantine/core";
import type { WorkOrder, WOStatus } from "./types";
import { pipelineData } from "./sampleData";
// ─── Constants ────────────────────────────────────────────────────────────────
const PRIORITY_COLORS: Record<string, string> = {
emergency: "#fa5252",
high: "#fd7e14",
normal: "#228be6",
low: "#868e96",
};
const STATUS_COLORS: Record<WOStatus, string> = {
new: "#228be6",
"pending-schedule": "#fab005",
scheduled: "#15aabf",
"in-progress": "#40c057",
"awaiting-parts": "#fd7e14",
"on-hold": "#868e96",
completed: "#868e96",
overdue: "#fa5252",
};
const PIPELINE_STAGES: { status: WOStatus; label: string }[] = [
{ status: "new", label: "New" },
{ status: "pending-schedule", label: "Pending Schedule" },
{ status: "scheduled", label: "Scheduled" },
{ status: "in-progress", label: "In Progress" },
{ status: "awaiting-parts", label: "Awaiting Parts" },
{ status: "overdue", label: "Overdue" },
{ status: "on-hold", label: "On Hold" },
{ status: "completed", label: "Completed" },
];
// ─── Sub-components ───────────────────────────────────────────────────────────
function PriorityDot({ priority }: { priority: string }) {
return (
<Box
style={{
width: 8,
height: 8,
borderRadius: "50%",
backgroundColor: PRIORITY_COLORS[priority] ?? "#868e96",
flexShrink: 0,
}}
/>
);
}
function WorkOrderCard({ wo }: { wo: WorkOrder }) {
const isOverdue = wo.daysInStatus > 7;
const isEmergency = wo.priority === "emergency";
return (
<Tooltip label={`${wo.customerName} — ${wo.id}`} position="top" withArrow openDelay={400}>
<Box
style={{
borderLeft:
isOverdue || isEmergency
? `3px solid ${isEmergency ? "#fa5252" : "#fd7e14"}`
: "3px solid transparent",
background: "var(--mantine-color-gray-0)",
borderRadius: 4,
padding: "5px 7px",
cursor: "default",
transition: "background 0.15s",
}}
onMouseEnter={(e) => {
(e.currentTarget as HTMLDivElement).style.background = "var(--mantine-color-gray-1)";
}}
onMouseLeave={(e) => {
(e.currentTarget as HTMLDivElement).style.background = "var(--mantine-color-gray-0)";
}}
>
<Group gap={5} wrap="nowrap" align="flex-start">
<PriorityDot priority={wo.priority} />
<Box style={{ flex: 1, minWidth: 0 }}>
<Text size="xs" fw={500} lineClamp={2} style={{ lineHeight: 1.3 }}>
{wo.title}
</Text>
<Group gap={4} mt={2}>
<Text size="xs" c="dimmed" style={{ fontSize: 10 }}>
{wo.id}
</Text>
{wo.daysInStatus > 0 && (
<Text
size="xs"
style={{
fontSize: 10,
color: wo.daysInStatus > 7 ? "#fa5252" : "var(--mantine-color-dimmed)",
fontWeight: wo.daysInStatus > 7 ? 600 : 400,
}}
>
{wo.daysInStatus}d
</Text>
)}
</Group>
</Box>
</Group>
</Box>
</Tooltip>
);
}
function PipelineColumn({
stage,
orders,
count,
}: {
stage: { status: WOStatus; label: string };
orders: WorkOrder[];
count: number;
}) {
const color = STATUS_COLORS[stage.status];
return (
<Box
style={{
minWidth: 190,
maxWidth: 210,
flex: "0 0 200px",
display: "flex",
flexDirection: "column",
height: "100%",
}}
>
{/* Column header */}
<Box
style={{
borderBottom: `2px solid ${color}`,
paddingBottom: 6,
marginBottom: 6,
}}
>
<Group gap={6} justify="space-between" wrap="nowrap">
<Text
size="xs"
fw={700}
style={{ color, textTransform: "uppercase", letterSpacing: "0.04em" }}
>
{stage.label}
</Text>
<Badge
size="xs"
radius="xl"
style={{
backgroundColor: color,
color: "#fff",
minWidth: 20,
textAlign: "center",
}}
>
{count}
</Badge>
</Group>
</Box>
{/* Cards */}
<ScrollArea style={{ flex: 1 }} scrollbarSize={4}>
<Stack gap={4}>
{orders.length === 0 ? (
<Text size="xs" c="dimmed" ta="center" py="xs" style={{ fontStyle: "italic" }}>
None
</Text>
) : (
orders.map((wo) => <WorkOrderCard key={wo.id} wo={wo} />)
)}
</Stack>
</ScrollArea>
</Box>
);
}
// ─── Main Component ───────────────────────────────────────────────────────────
export function Variant1_FunnelPipeline() {
const { workOrders, statusCounts } = pipelineData;
const totalOpen = workOrders.filter((wo) => wo.status !== "completed").length;
// Group work orders by status
const ordersByStatus = PIPELINE_STAGES.reduce<Record<WOStatus, WorkOrder[]>>(
(acc, stage) => {
acc[stage.status] = workOrders.filter((wo) => wo.status === stage.status);
return acc;
},
{} as Record<WOStatus, WorkOrder[]>,
);
return (
<Paper
withBorder
shadow="sm"
radius="md"
p="md"
style={{ display: "flex", flexDirection: "column", height: "100%" }}
>
{/* Tile header */}
<Group justify="space-between" mb="sm" wrap="nowrap">
<Box>
<Text fw={700} size="sm">
Work Order Pipeline
</Text>
<Text size="xs" c="dimmed">
Live status across all active orders
</Text>
</Box>
<Group gap={6} wrap="nowrap">
<Badge variant="light" color="blue" size="sm">
{totalOpen} open
</Badge>
<Badge variant="light" color="gray" size="sm">
{statusCounts.completed} completed
</Badge>
</Group>
</Group>
{/* Priority legend */}
<Group gap={10} mb="sm" wrap="nowrap">
{Object.entries(PRIORITY_COLORS).map(([p, c]) => (
<Group gap={4} key={p} wrap="nowrap">
<Box style={{ width: 8, height: 8, borderRadius: "50%", backgroundColor: c }} />
<Text size="xs" c="dimmed" style={{ textTransform: "capitalize" }}>
{p}
</Text>
</Group>
))}
<Text size="xs" c="dimmed" style={{ marginLeft: "auto" }}>
Red border = overdue (>7d)
</Text>
</Group>
{/* Pipeline columns */}
<ScrollArea style={{ flex: 1 }} scrollbarSize={6} type="always" offsetScrollbars>
<Box
style={{
display: "flex",
gap: 10,
height: 420,
paddingBottom: 4,
}}
>
{PIPELINE_STAGES.map((stage) => (
<PipelineColumn
key={stage.status}
stage={stage}
orders={ordersByStatus[stage.status]}
count={statusCounts[stage.status]}
/>
))}
</Box>
</ScrollArea>
</Paper>
);
}