An interactive demonstration of field technician status tracking and availability. Explore avatar-based status grids, progress lists, and region-grouped cards.
Tech Availability Laboratory
Monitor technician availability statuses, active jobs, skill tags, and geographic zones.
Marcus Rivera
North
Sandra Chen
North
Apex Industrial
ETA 14:30
Derek Okafor
South
Greenfield Estates
ETA 13:45
Priya Nair
East
Tom Wakefield
West
Leila Vasquez
East
Summit Office Park
ETA 15:15
James Thornton
South
Brittany Simmons
West
Variant1_AvatarStatusGrid.tsx (Widget Implementation)
import {
Paper,
Group,
Stack,
Text,
Badge,
Avatar,
SimpleGrid,
ScrollArea,
Title,
Tooltip,
} from "@mantine/core";
import { HiMapPin, HiClock, HiCheckBadge } from "react-icons/hi2";
import type { TechnicianAvailability } from "./types";
import { technicians, STATUS_COLORS, STATUS_LABELS } from "./sampleData";
const RING_WIDTH = 3;
function TechCard({ tech }: { tech: TechnicianAvailability }) {
const ringColor = STATUS_COLORS[tech.status];
const statusLabel = STATUS_LABELS[tech.status];
const statusBadgeColor: Record<string, string> = {
available: "green",
"on-job": "blue",
"on-break": "yellow",
"off-duty": "gray",
pto: "grape",
};
return (
<Paper
withBorder
radius="md"
p="sm"
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 8,
minHeight: 180,
}}
>
{/* Avatar with colored ring */}
<div
style={{
borderRadius: "50%",
padding: RING_WIDTH,
background: ringColor,
display: "inline-flex",
marginTop: 4,
}}
>
<Avatar
radius="xl"
size="lg"
style={{ background: "#e9ecef", color: "#495057", fontWeight: 700 }}
>
{tech.initials}
</Avatar>
</div>
{/* Name + zone */}
<Stack gap={2} align="center" style={{ width: "100%" }}>
<Text fw={600} size="sm" ta="center" lineClamp={1}>
{tech.name}
</Text>
<Group gap={4} justify="center">
<HiMapPin size={11} color="#868e96" />
<Text size="xs" c="dimmed">
{tech.zone}
</Text>
</Group>
</Stack>
{/* Status badge */}
<Badge size="xs" color={statusBadgeColor[tech.status]} variant="light" radius="sm">
{statusLabel}
</Badge>
{/* On-job current customer + ETA */}
{tech.status === "on-job" && tech.currentJob && (
<Stack gap={2} align="center" style={{ width: "100%" }}>
<Text size="xs" fw={500} ta="center" lineClamp={1} c="blue.7">
{tech.currentJob.customerName}
</Text>
<Group gap={3} justify="center">
<HiClock size={11} color="#868e96" />
<Text size="xs" c="dimmed">
ETA {tech.currentJob.estimatedCompletion}
</Text>
</Group>
</Stack>
)}
{/* Skill chips */}
<Group gap={4} justify="center" wrap="wrap" mt="auto">
{tech.skills.map((skill) => (
<Tooltip
key={skill.name}
label={skill.certified ? `${skill.name} — Certified` : `${skill.name} — Uncertified`}
withArrow
position="top"
>
<Badge
size="xs"
variant={skill.certified ? "filled" : "outline"}
color="gray"
radius="sm"
style={{ cursor: "default" }}
leftSection={skill.certified ? <HiCheckBadge size={9} /> : undefined}
>
{skill.name}
</Badge>
</Tooltip>
))}
</Group>
</Paper>
);
}
export function Variant1_AvatarStatusGrid() {
const availableCount = technicians.filter((t) => t.status === "available").length;
return (
<Paper withBorder shadow="sm" radius="md" p="md" style={{ width: "100%" }}>
{/* Tile header */}
<Group justify="space-between" mb="md" align="center">
<Title order={5} style={{ letterSpacing: -0.3 }}>
Technician Status
</Title>
<Badge color="green" variant="light" size="md" radius="sm">
{availableCount} Available
</Badge>
</Group>
<ScrollArea style={{ maxHeight: 480 }} scrollbarSize={6}>
<SimpleGrid cols={3} spacing="sm">
{technicians.map((tech) => (
<TechCard key={tech.id} tech={tech} />
))}
</SimpleGrid>
</ScrollArea>
</Paper>
);
}