// Towbook-specific patterns: status pill, call card, driver row, equipment inspection row, photo capture, signature, bottom action bar.
const { useState: useStateP } = React;
// Status pill — semantic for tow ops. Variant: 'pill' (rounded), 'tag' (square left number tag).
function TBStatusPill({ status = 'waiting', variant = 'pill', size = 'md' }) {
const dark = typeof document !== 'undefined' && document.documentElement.getAttribute('data-theme') === 'dark';
const palette = dark ? TBT.statusDark[status] : TBT.status[status];
const label = TBT.status[status]?.label || status;
const fs = size === 'sm' ? 11 : 12;
const padY = size === 'sm' ? 2 : 3;
const padX = size === 'sm' ? 8 : 10;
return (
{label}
);
}
// Call card — the dispatch list row. Borrows from screenshots: green/blue/amber accent strip,
// number tag, status sentence, vehicle, account, time + ETA, address(es), action bar.
function TBCallCard({ call, onEdit, onUpdateStatus, onCallContact, onClick, expanded = true }) {
const dark = typeof document !== 'undefined' && document.documentElement.getAttribute('data-theme') === 'dark';
const status = call.status;
const palette = dark ? TBT.statusDark[status] : TBT.status[status];
return (
{/* header line: number tag + status sentence */}
{call.number}
{call.statusSentence}
{/* call type */}
{call.type}
{/* vehicle line */}
{call.vehicleYear} {call.vehicleMakeModel} ({call.vehicleColor})
{/* account */}
{call.account}
{/* time row */}
{call.receivedTime}
({call.timeAgo})
{call.eta && (
<>
ETA {call.eta}
>
)}
{expanded && (
<>
{/* addresses */}
{call.pickup}
{call.dropoff}
{/* action bar */}
{ e.stopPropagation(); onEdit?.(call); }}/>
{ e.stopPropagation(); onUpdateStatus?.(call); }}/>
{onCallContact && { e.stopPropagation(); onCallContact?.(call); }}/>}
>
)}
);
}
function CardAction({ icon, label, onClick }) {
const [pressed, setPressed] = useStateP(false);
return (
);
}
// Driver row — for assignment / map driver list.
function TBDriverRow({ name, unit, status, onClick, distance }) {
return (
{(name || '').split(' ').map(s => s[0]).join('').slice(0,2)}}
headline={name}
supporting={`Unit ${unit}${distance ? ' · ' + distance : ''}`}
trailing={}
onClick={onClick}
/>
);
}
// Equipment inspection row — checklist with Pass / Fail / N/A + camera.
function TBInspectionRow({ label, value, onChange, photoCount = 0, onPhoto }) {
const opts = [
{ key: 'pass', label: 'Pass', activeBg: 'var(--green-3)', activeFg: 'var(--green-11)' },
{ key: 'fail', label: 'Fail', activeBg: 'var(--red-3)', activeFg: 'var(--red-11)' },
{ key: 'na', label: 'N/A', activeBg: 'var(--slate-3)', activeFg: 'var(--slate-12)' },
];
return (
{label}
{opts.map((o, i) => {
const active = value === o.key;
return (
);
})}
);
}
// Photo capture tile (used in call detail / inspection)
function TBPhotoTile({ src, label, onClick, count }) {
return (
);
}
// Bottom action bar (used in call detail — Change Status / Add Photo / Show Map / More)
function TBBottomActionBar({ items }) {
return (
{items.map((it, i) => (
))}
);
}
// Section header — in detail screens
function TBSectionHeader({ children, action }) {
return (
{children}
{action}
);
}
// Detail row — label / value pair used in call detail
function TBDetailRow({ icon, label, value, action }) {
return (
{icon &&
{React.createElement(icon, { size: 18 })}
}
{label &&
{label}
}
{value}
{action}
);
}
// Pull-to-refresh indicator (visual only; logic shown via tweak/animation)
function TBPullToRefresh({ pulling = false, refreshing = false }) {
return (
);
}
Object.assign(window, {
TBStatusPill, TBCallCard, TBDriverRow, TBInspectionRow,
TBPhotoTile, TBBottomActionBar, TBSectionHeader, TBDetailRow,
TBPullToRefresh,
});