Driver Inspection Checklist
blocklogisticsCommercial vehicle Driver Vehicle Inspection Report (DVIR) pre-trip and post-trip safety audit. Features vehicle odometer & ELD telemetry header, 5 grouped FMCSA safety inspection categories (Brakes & Air Pressure, Tires & Wheels, Lights & Electrical, Engine & Fluids, Emergency Safety Gear) with Pass/Fail/N/A segmented controls, dynamic defect report card with Out-of-Service severity classification, defect notes, photo dropzone, driver CDL certification signature pad, and post-submission compliance receipt.
Installation
$pnpm dlx uipkge-ng@latest add driver-inspection-checklist$npx uipkge-ng@latest add driver-inspection-checklist$yarn dlx uipkge-ng@latest add driver-inspection-checklist$bunx uipkge-ng@latest add driver-inspection-checklistManifest:
https://uipkge.dev/r/angular/driver-inspection-checklist.jsonInstalls to:src/app/components/blocks/driver-inspection-checklist/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
InspectionIteminterface InspectionItem {
id: string
label: string
spec: string
status: CheckStatus
notes?: string
}InspectionCategoryinterface InspectionCategory {
id: string
title: string
description: string
icon: string
items: InspectionItem[]
}DefectPhotointerface DefectPhoto {
id: string
name: string
size: string
url?: string
}DVIRReportDatainterface DVIRReportData {
reportId: string
vehicleId: string
trailerId: string
odometer: string
inspectionType: InspectionType
driverName: string
driverCdl: string
carrierName: string
dotNumber: string
inspectionDate: string
inspectionTime: string
location: string
categories: InspectionCategory[]
defectSeverity: DefectSeverity
defectNotes: string
defectPhotos: DefectPhoto[]
certified: boolean
signatureTimestamp: string
}Files installed (2)
src/app/components/blocks/driver-inspection-checklist/driver-inspection-checklist.component.ts50.8 kB
// Commercial vehicle Driver Vehicle Inspection Report (DVIR) pre-trip and post-trip safety // audit. Features vehicle odometer & ELD telemetry header, 5 grouped FMCSA safety inspection // categories (Brakes & Air Pressure, Tires & Wheels, Lights & Electrical, Engine & Fluids, // Emergency Safety Gear) with Pass/Fail/N/A segmented controls, dynamic defect report card with // Out-of-Service severity classification, defect notes, photo dropzone, driver CDL certification // signature pad, and post-submission compliance receipt. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core' import { AlertCircle, AlertTriangle, Camera, Check, CheckCircle2, Clock, Disc, Download, FileCheck, FileText, Flame, Gauge, Layers, Lightbulb, LucideAngularModule, Minus, Printer, RotateCcw, ShieldAlert, ShieldCheck, Trash2, Truck, UploadCloud, User, Wrench, X, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiLabelComponent } from '@/ui/label/label.component' import { UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, } from '@/ui/select/select.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTextareaComponent } from '@/ui/textarea/textarea.component' export type CheckStatus = 'pass' | 'fail' | 'na' export type DefectSeverity = 'minor' | 'critical' export type InspectionType = 'pre-trip' | 'post-trip' | 'intermodal' export interface InspectionItem { id: string label: string spec: string status: CheckStatus notes?: string } export interface InspectionCategory { id: string title: string description: string icon: string items: InspectionItem[] } export interface DefectPhoto { id: string name: string size: string url?: string } export interface DVIRReportData { reportId: string vehicleId: string trailerId: string odometer: string inspectionType: InspectionType driverName: string driverCdl: string carrierName: string dotNumber: string inspectionDate: string inspectionTime: string location: string categories: InspectionCategory[] defectSeverity: DefectSeverity defectNotes: string defectPhotos: DefectPhoto[] certified: boolean signatureTimestamp: string } const DEFAULT_CATEGORIES: InspectionCategory[] = [ { id: 'brakes', title: '1. Brakes & Air Pressure', description: 'FMCSA § 393.40 / 393.51 Air Brake System & Lines', icon: 'disc', items: [ { id: 'brakes-service', label: 'Service Brakes & Stopping Response', spec: 'Smooth pedal feel, no pulling, stopping within 35 ft at 20 mph', status: 'pass', }, { id: 'brakes-parking', label: 'Parking Brake Mechanism', spec: 'Holds fully loaded vehicle against low gear engine torque', status: 'pass', }, { id: 'brakes-airlines', label: 'Air Lines & System Pressure (120 PSI)', spec: 'Maintains 120 PSI operating pressure; gladhand seals leak-free', status: 'pass', }, { id: 'brakes-drums', label: 'Brake Drums, Linings & Slack Adjusters', spec: 'Lining thickness > 1/4", no oil/grease contamination on drums', status: 'pass', }, ], }, { id: 'tires', title: '2. Tires, Wheels & Rims', description: 'FMCSA § 393.75 Wheel Assemblies & Tread Integrity', icon: 'layers', items: [ { id: 'tires-tread', label: 'Steer & Drive Tire Tread Depth', spec: 'Steer tires > 4/32" tread; drive/trailer tires > 2/32" tread', status: 'pass', }, { id: 'tires-pressure', label: 'Tire Pressure & Cold Inflation (100 PSI)', spec: 'All 10 dual/steer tires at 100 PSI cold; valve caps sealed', status: 'pass', }, { id: 'tires-lugnuts', label: 'Lug Nuts & Wheel Rim Fasteners', spec: 'All wheel studs present & torqued; no rust trails or rim cracks', status: 'pass', }, { id: 'tires-hubseals', label: 'Wheel Hub Oil Seals & Bearings', spec: 'Oil level visible in sight glass; hub seals dry with no leaks', status: 'pass', }, ], }, { id: 'lights', title: '3. Lights & Electrical Systems', description: 'FMCSA § 393.9 Lamps, Reflective Devices & Electrical Wiring', icon: 'lightbulb', items: [ { id: 'lights-headlights', label: 'Headlights (Low & High Beams)', spec: 'Both sealed beams operable; lenses clean and properly aimed', status: 'pass', }, { id: 'lights-turnsignals', label: 'Turn Signals & 4-Way Hazard Flashers', spec: 'Front, cab-side, and rear flashers functional on tractor & trailer', status: 'pass', }, { id: 'lights-brakelights', label: 'Brake Lights & Tail Lights', spec: 'Instant illumination upon pedal depression; lenses intact', status: 'pass', }, { id: 'lights-clearance', label: 'Clearance Lights & DOT Reflectors', spec: 'Amber front/sides, red rear; DOT-C2 reflective sheeting clean', status: 'pass', }, ], }, { id: 'engine', title: '4. Engine Compartment & Fluids', description: 'Powertrain fluids, radiator integrity, belts & steering gear', icon: 'wrench', items: [ { id: 'engine-oil', label: 'Engine Oil Level & Quality', spec: 'Dipstick level in safe crosshatch; oil clean without burnt odor', status: 'pass', }, { id: 'engine-coolant', label: 'Engine Coolant & Radiator Core', spec: 'Surge tank level at MAX line; radiator cap tight, no hose weeping', status: 'pass', }, { id: 'engine-powersteering', label: 'Power Steering Fluid & Reservoir', spec: 'Fluid level within cold fill mark; pump operates without whine', status: 'pass', }, { id: 'engine-belts', label: 'Serpentine Belts & Cooling Hoses', spec: 'Belt deflection < 1/2"; no rib fraying, cracks, or soft hoses', status: 'pass', }, ], }, { id: 'safety', title: '5. Emergency & Safety Equipment', description: 'FMCSA § 393.95 Emergency Equipment in Commercial Motor Vehicles', icon: 'flame', items: [ { id: 'safety-extinguisher', label: 'Fire Extinguisher (Charged & Tagged)', spec: 'Minimum 5 B:C rating; pressure needle in green, annual tag valid', status: 'pass', }, { id: 'safety-triangles', label: 'Reflective Warning Triangles (3 Pack)', spec: 'Three bidirectional red reflective triangles present & secured', status: 'pass', }, { id: 'safety-firstaid', label: 'First Aid & Spill Response Kit', spec: 'Sealed commercial first aid pack, eye wash, HazMat pads', status: 'pass', }, { id: 'safety-wipers', label: 'Windshield Wipers, Washers & Horn', spec: 'Clean sweep without streaks; air horn and electric city horn loud', status: 'pass', }, ], }, ] const DEFECT_SEED_NOTE = 'Air supply line to secondary reservoir exhibits slight pressure drop (down to 105 PSI under load). Audible hiss near tractor-trailer gladhand seal.' @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-driver-inspection-checklist, [ui-driver-inspection-checklist]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardFooterComponent, UiCardHeaderComponent, UiCardTitleComponent, UiCheckboxComponent, UiInputComponent, UiLabelComponent, UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, UiSeparatorComponent, UiTextareaComponent, ], template: ` <div data-slot="driver-inspection-checklist" [class]="rootClass"> @if (submitted()) { <div class="space-y-6"> <div ui-card class="border-border overflow-hidden shadow-xs"> <div [class]="receiptBannerClass"> <div class="relative mx-auto mb-4 size-16"> <span [class]="receiptGlowClass" aria-hidden="true"></span> <span [class]="receiptIconWrapClass"> @if (hasDefects()) { <lucide-icon [img]="ShieldAlert" class="size-8" aria-hidden="true" /> } @else { <lucide-icon [img]="CheckCircle2" class="size-8" aria-hidden="true" /> } </span> </div> <span ui-badge [variant]="hasDefects() ? 'destructive' : 'outline'" [class]="receiptBadgeClass"> FMCSA § 396.11 Certified Electronic DVIR </span> <h2 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl"> {{ hasDefects() ? 'DVIR Transmitted · Defect Action Required' : 'DVIR Certified · Ready for Dispatch' }} </h2> <p class="text-muted-foreground mx-auto mt-1.5 max-w-xl text-xs sm:text-sm"> {{ hasDefects() ? 'Inspection report logged with safety defects. Maintenance dispatch notification has been dispatched to the terminal garage desk.' : 'Commercial motor vehicle safety inspection verified with zero safety defects. Safe to operate under FMCSA regulations.' }} </p> <div class="border-border bg-background/80 mt-4 inline-flex flex-wrap items-center gap-2 rounded-full border px-3.5 py-1 text-xs backdrop-blur-xs" > <span class="text-muted-foreground font-medium">Compliance Document Ref:</span> <span class="text-foreground font-mono font-bold">{{ reportId }}</span> </div> </div> <div ui-card-content class="space-y-6 p-6"> <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div class="border-border bg-muted/20 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs font-medium">Vehicle / Tractor</span> <span class="text-foreground mt-0.5 block text-sm font-semibold">{{ vehicleId }}</span> <span class="text-muted-foreground text-xs">{{ odometer }}</span> </div> <div class="border-border bg-muted/20 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs font-medium">Associated Trailer</span> <span class="text-foreground mt-0.5 block text-sm font-semibold">{{ trailerId }}</span> <span class="text-muted-foreground text-xs">Type: 53-ft Dry Van</span> </div> <div class="border-border bg-muted/20 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs font-medium">Certified Driver</span> <span class="text-foreground mt-0.5 block text-sm font-semibold">{{ driverName }}</span> <span class="text-muted-foreground font-mono text-xs">CDL #{{ driverCdl }}</span> </div> <div class="border-border bg-muted/20 rounded-lg border p-3"> <span class="text-muted-foreground block text-xs font-medium">Timestamp & Hub</span> <span class="text-foreground mt-0.5 block text-sm font-semibold"> {{ inspectionDate }} · {{ inspectionTime }} </span> <span class="text-muted-foreground block truncate text-xs">{{ location }}</span> </div> </div> <div class="border-border bg-card flex flex-wrap items-center justify-between gap-3 rounded-lg border p-4" > <div class="flex flex-wrap items-center gap-x-6 gap-y-3"> <div> <span class="text-muted-foreground block text-xs">Items Inspected</span> <span class="text-foreground text-lg font-bold tabular-nums"> {{ totalCount() }} / {{ totalCount() }} </span> </div> <ui-separator orientation="vertical" class="h-8" /> <div> <span class="text-muted-foreground block text-xs">Passed</span> <span class="text-success text-lg font-bold tabular-nums">{{ passCount() }}</span> </div> <ui-separator orientation="vertical" class="h-8" /> <div> <span class="text-muted-foreground block text-xs">Failed / Defects</span> <span [class]="receiptFailClass">{{ failCount() }}</span> </div> <ui-separator orientation="vertical" class="h-8" /> <div> <span class="text-muted-foreground block text-xs">N/A</span> <span class="text-muted-foreground text-lg font-bold tabular-nums">{{ naCount() }}</span> </div> </div> <span ui-badge [variant]="hasDefects() ? 'destructive' : 'outline'" class="gap-1.5 px-3 py-1 text-xs"> @if (!hasDefects()) { <lucide-icon [img]="CheckCircle2" class="size-3.5" /> } @else { <lucide-icon [img]="AlertTriangle" class="size-3.5" /> } {{ hasDefects() ? 'Defects Found (' + failCount() + ')' : 'All Systems Verified' }} </span> </div> <div class="border-border bg-muted/15 space-y-2 rounded-xl border p-4"> <div class="flex items-center justify-between"> <span class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" /> Electronic Cryptographic Driver Signature </span> <span class="text-muted-foreground font-mono text-xs">DOT § 396.11 Certified</span> </div> <div class="border-border/60 border-b pt-1 pb-3"> <p class="text-primary text-2xl font-medium tracking-wide italic"> {{ typedSignature() || driverName }} </p> </div> <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-2 font-mono text-xs"> <span>Signer: {{ driverName }} · CDL #{{ driverCdl }}</span> <span>SHA-256: 8f9b7c2d-9482-41e9-b631-{{ reportId }}</span> </div> </div> <div class="flex flex-wrap items-center justify-between gap-3 pt-2"> <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs font-medium" (click)="handleReset()" > <lucide-icon [img]="RotateCcw" class="size-3.5" /> Start New Inspection </button> <div class="flex flex-wrap items-center gap-2"> <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs font-medium"> <lucide-icon [img]="Printer" class="size-3.5" /> Print Driver Copy </button> <button ui-button aria-label="Download attachment" size="sm" class="gap-1.5 text-xs font-medium"> <lucide-icon [img]="Download" class="size-3.5" /> Download Certified DVIR PDF </button> </div> </div> </div> </div> </div> } @else { <div class="space-y-6"> <!-- Top Inspection Header Card --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge variant="outline" class="border-primary/30 bg-primary/10 text-primary gap-1.5 py-0.5 text-xs font-medium" > <lucide-icon [img]="Truck" class="size-3.5" aria-hidden="true" /> FMCSA § 396.11 Audit </span> @if (hasDefects()) { <span ui-badge variant="destructive" class="animate-pulse gap-1.5 py-0.5 text-xs font-medium"> <lucide-icon [img]="AlertTriangle" class="size-3.5" aria-hidden="true" /> Defects Found · {{ failCount() }} {{ failCount() === 1 ? 'Item' : 'Items' }} Failed </span> } @else { <span ui-badge variant="outline" class="border-warning/40 bg-warning/10 text-warning gap-1.5 py-0.5 text-xs font-medium" > <lucide-icon [img]="Clock" class="size-3.5" aria-hidden="true" /> Inspection In Progress </span> } </div> <h3 ui-card-title class="text-xl font-bold tracking-tight sm:text-2xl"> Driver Vehicle Inspection Report (DVIR) </h3> <p ui-card-description class="text-xs sm:text-sm"> Mandatory pre-trip and post-trip commercial motor vehicle safety certification and defect tracking portal. </p> </div> <div class="flex flex-wrap items-center gap-2"> <button type="button" ui-button variant="outline" size="sm" class="gap-1.5 text-xs font-medium" (click)="markAllPass()" > <lucide-icon [img]="CheckCircle2" class="text-success size-3.5" aria-hidden="true" /> Quick Pass All </button> <button type="button" ui-button size="sm" class="gap-1.5 text-xs font-medium" [disabled]="!certified() || !typedSignature().trim()" (click)="handleSubmit()" > <lucide-icon [img]="FileCheck" class="size-3.5" aria-hidden="true" /> Submit Completed DVIR </button> </div> </div> </div> <div ui-card-content class="pt-0 pb-5"> <div class="border-border bg-muted/20 grid gap-3 rounded-lg border p-4 sm:grid-cols-2 lg:grid-cols-4"> <div class="space-y-1"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Truck" class="text-primary size-3.5" /> <span>Vehicle / Unit Number</span> </div> <div class="text-foreground text-xs font-semibold">{{ vehicleId }}</div> <div class="text-muted-foreground text-xs">Trailer: {{ trailerId }}</div> </div> <div class="space-y-1"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Gauge" class="text-primary size-3.5" /> <span>Odometer Mileage</span> </div> <div class="text-foreground text-xs font-semibold tabular-nums">{{ odometer }}</div> <div class="text-muted-foreground text-xs">ECM Telemetry Synced</div> </div> <div class="space-y-1"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="FileText" class="text-primary size-3.5" /> <span>Inspection Type</span> </div> <ui-select [value]="inspectionType()" (valueChange)="setInspectionType($event)"> <button ui-select-trigger class="h-7 text-xs font-medium"> <ui-select-value /> </button> <ui-select-content> <ui-select-item value="pre-trip">Pre-Trip Safety Inspection</ui-select-item> <ui-select-item value="post-trip">Post-Trip Safety Audit</ui-select-item> <ui-select-item value="intermodal">Intermodal Chassis Inspection</ui-select-item> </ui-select-content> </ui-select> </div> <div class="space-y-1"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="User" class="text-primary size-3.5" /> <span>Driver & Terminal</span> </div> <div class="text-foreground text-xs font-semibold">{{ driverName }} · CDL #{{ driverCdl }}</div> <div class="text-muted-foreground truncate text-xs">{{ location }}</div> </div> </div> <div class="mt-4 space-y-1.5"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Audit Completion Progress</span> <span class="text-foreground font-mono font-medium tabular-nums"> {{ passCount() }} Pass · {{ failCount() }} Fail · {{ naCount() }} N/A ({{ totalCount() }} Total) </span> </div> <div class="bg-muted h-1.5 w-full overflow-hidden rounded-full"> <div [class]="progressBarClass" [style.width]="completionPercentage() + '%'"></div> </div> </div> </div> </div> <!-- 5 FMCSA INSPECTION CATEGORIES --> <div class="space-y-4"> @for (category of categories(); track category.id) { <div ui-card class="border-border overflow-hidden shadow-xs"> <div ui-card-header class="bg-muted/15 border-border border-b px-5 py-3.5"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="flex flex-wrap items-center gap-2.5"> <div class="border-primary/20 bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-md border" > @if (category.icon === 'disc') { <lucide-icon [img]="Disc" class="size-4" /> } @else if (category.icon === 'layers') { <lucide-icon [img]="Layers" class="size-4" /> } @else if (category.icon === 'lightbulb') { <lucide-icon [img]="Lightbulb" class="size-4" /> } @else if (category.icon === 'wrench') { <lucide-icon [img]="Wrench" class="size-4" /> } @else if (category.icon === 'flame') { <lucide-icon [img]="Flame" class="size-4" /> } </div> <div> <h3 ui-card-title class="text-sm font-bold tracking-tight sm:text-base"> {{ category.title }} </h3> <p ui-card-description class="text-xs">{{ category.description }}</p> </div> </div> <div class="flex flex-wrap items-center gap-2 self-end sm:self-auto"> <button type="button" ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground h-7 text-xs" (click)="setCategoryAll(category.id, 'pass')" > Pass All </button> <button type="button" ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground h-7 text-xs" (click)="setCategoryAll(category.id, 'na')" > N/A All </button> </div> </div> </div> <div ui-card-content class="divide-border divide-y p-0"> @for (item of category.items; track item.id) { <div [class]="itemRowClass(item.status)"> <div class="min-w-0 space-y-1 pr-2"> <div class="flex flex-wrap items-center gap-2"> <span class="text-foreground text-xs font-semibold sm:text-sm">{{ item.label }}</span> @if (item.status === 'fail') { <span ui-badge variant="destructive" class="h-4 py-0 font-mono text-xs uppercase"> Defect </span> } </div> <p class="text-muted-foreground text-xs leading-relaxed">{{ item.spec }}</p> </div> <div class="flex shrink-0 items-center gap-1 self-start sm:self-center"> <button type="button" [attr.aria-label]="'Mark ' + item.label + ' as Pass'" [class]="segmentClass(item.status, 'pass')" (click)="setItemStatus(category.id, item.id, 'pass')" > <lucide-icon [img]="Check" class="size-3.5" aria-hidden="true" /> Pass </button> <button type="button" [attr.aria-label]="'Mark ' + item.label + ' as Fail'" [class]="segmentClass(item.status, 'fail')" (click)="setItemStatus(category.id, item.id, 'fail')" > <lucide-icon [img]="X" class="size-3.5" aria-hidden="true" /> Fail </button> <button type="button" [attr.aria-label]="'Mark ' + item.label + ' as Not Applicable'" [class]="segmentClass(item.status, 'na')" (click)="setItemStatus(category.id, item.id, 'na')" > <lucide-icon [img]="Minus" class="size-3.5" aria-hidden="true" /> N/A </button> </div> </div> } </div> </div> } </div> <!-- DYNAMIC DEFECT REPORT CARD --> @if (hasDefects()) { <div ui-card class="border-destructive/40 bg-destructive/5 dark:bg-destructive/10 overflow-hidden shadow-xs transition-colors duration-200" > <div ui-card-header class="border-destructive/20 bg-destructive/10 border-b pb-4"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div class="flex flex-wrap items-center gap-2.5"> <div class="bg-destructive/20 text-destructive flex size-8 shrink-0 items-center justify-center rounded-md" > <lucide-icon [img]="AlertTriangle" class="size-4.5" aria-hidden="true" /> </div> <div> <h3 ui-card-title class="text-destructive text-base font-bold"> Critical Defect Log & Repair Dispatch Form </h3> <p ui-card-description class="text-destructive/80 text-xs"> {{ failCount() }} item(s) flagged with safety defects requiring mechanic sign-off under FMCSA § 396.13. </p> </div> </div> <span ui-badge variant="destructive" class="font-mono text-xs uppercase">Action Required</span> </div> </div> <div ui-card-content class="space-y-4 p-5"> <div class="space-y-1.5"> <label ui-label class="text-foreground text-xs font-semibold">Defective Components Identified</label> <div class="flex flex-wrap gap-2"> @for (item of failedItems(); track item.id) { <span class="border-destructive/30 bg-destructive/10 text-destructive inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium" > <lucide-icon [img]="AlertCircle" class="size-3.5 shrink-0" /> {{ item.label }} </span> } </div> </div> <div class="grid gap-4 sm:grid-cols-2"> <div class="min-w-0 space-y-1.5"> <label ui-label for="defect-severity-ng" class="text-xs font-semibold"> Defect Severity Classification * </label> <ui-select [value]="defectSeverity()" (valueChange)="setDefectSeverity($event)"> <button ui-select-trigger id="defect-severity-ng" class="h-9 w-full min-w-0 text-xs [&>span]:truncate" > <ui-select-value /> </button> <ui-select-content> <ui-select-item value="minor"> Minor Defect — Safe to operate; scheduled for terminal shop </ui-select-item> <ui-select-item value="critical"> Out-of-Service (OOS) Critical — Dispatch prohibited until repaired </ui-select-item> </ui-select-content> </ui-select> </div> <div class="space-y-1.5"> <label ui-label class="text-xs font-semibold">Fleet Maintenance Desk Dispatch</label> <div class="border-border bg-card flex flex-wrap items-center gap-2 rounded-md border p-2 text-xs"> <ui-checkbox id="desk-notify-ng" [checked]="defectReportedToDesk()" (checkedChange)="defectReportedToDesk.set(!!$event)" /> <label ui-label for="desk-notify-ng" class="cursor-pointer text-xs font-normal"> Auto-create Priority Work Order in Garage Portal (Ticket #WO-8910) </label> </div> </div> </div> <div class="space-y-1.5"> <label ui-label for="defect-notes-ng" class="text-xs font-semibold"> Detailed Defect Description & Driver Notes * </label> <ui-textarea id="defect-notes-ng" [value]="defectNotes()" (valueChange)="defectNotes.set($event)" [rows]="3" placeholder="Describe exact defect location, leak sounds, tire damage, PSI measurements, or electrical malfunction..." class="text-xs" /> </div> <div class="space-y-2"> <div class="flex items-center justify-between"> <label ui-label class="flex items-center gap-1.5 text-xs font-semibold"> <lucide-icon [img]="Camera" class="text-primary size-3.5" /> Defect Photographic Evidence </label> <span class="text-muted-foreground text-xs">JPG, PNG up to 15MB</span> </div> <div role="button" tabindex="0" aria-label="Upload photo" class="border-border/80 hover:border-primary/50 focus-visible:ring-ring bg-card cursor-pointer rounded-xl border-2 border-dashed p-4 text-center transition-colors outline-none focus-visible:ring-2" (click)="addMockPhoto()" (keydown)="onDropzoneKeydown($event)" > <div class="flex flex-col items-center justify-center gap-1.5 py-2"> <div class="bg-primary/10 text-primary flex size-9 items-center justify-center rounded-full"> <lucide-icon [img]="UploadCloud" class="size-4.5" /> </div> <p class="text-foreground text-xs font-medium"> Click to attach photo or drag and drop image here </p> <p class="text-muted-foreground text-xs"> Capture close-ups of damaged tires, fluid puddles, broken lights, or air fittings </p> </div> </div> @if (defectPhotos().length > 0) { <div class="grid gap-2 sm:grid-cols-2"> @for (photo of defectPhotos(); track photo.id) { <div class="border-border bg-card flex items-center justify-between rounded-lg border px-3 py-2 text-xs" > <div class="flex flex-wrap items-center gap-2 truncate"> <lucide-icon [img]="Camera" class="text-primary size-3.5 shrink-0" /> <span class="text-foreground truncate font-medium">{{ photo.name }}</span> <span class="text-muted-foreground text-xs">({{ photo.size }})</span> </div> <button type="button" aria-label="Remove photo" class="text-muted-foreground hover:text-destructive focus-visible:ring-ring cursor-pointer rounded-sm p-1 transition-colors outline-none focus-visible:ring-2" (click)="removePhoto(photo.id)" > <lucide-icon [img]="Trash2" class="size-3.5" /> </button> </div> } </div> } </div> </div> </div> } <!-- DRIVER CERTIFICATION & SIGNATURE --> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="pb-3"> <div class="flex items-center justify-between"> <div class="flex flex-wrap items-center gap-2"> <lucide-icon [img]="ShieldCheck" class="text-primary size-4" /> <h3 ui-card-title class="text-base font-bold">Driver Safety Certification & Electronic Signature</h3> </div> <span ui-badge variant="outline" class="text-primary border-primary/30 font-mono text-xs"> FMCSA § 396.11(a) </span> </div> <p ui-card-description class="text-xs"> Commercial motor vehicle driver must certify that all required items have been inspected and all known defects disclosed. </p> </div> <div ui-card-content class="space-y-4"> <div class="border-border bg-muted/20 rounded-lg border p-3.5"> <div class="flex items-start gap-3"> <ui-checkbox id="cert-check-ng" [checked]="certified()" (checkedChange)="certified.set(!!$event)" class="mt-0.5" /> <div class="space-y-1"> <label ui-label for="cert-check-ng" class="text-foreground cursor-pointer text-xs leading-relaxed font-semibold" > I certify that I have conducted a thorough safety inspection of this vehicle and trailer in compliance with Federal Motor Carrier Safety Regulations. </label> <p class="text-muted-foreground text-xs leading-relaxed"> All components listed above have been checked. Any conditions likely to affect the safe operation of this vehicle or result in mechanical breakdown have been truthfully recorded. </p> </div> </div> </div> <div class="grid gap-3 sm:grid-cols-3"> <div class="space-y-1.5 sm:col-span-2"> <label ui-label for="driver-sig-ng" class="text-xs font-semibold"> Type Full Legal Name as Electronic Signature * </label> <ui-input id="driver-sig-ng" [value]="typedSignature()" (valueChange)="typedSignature.set($event)" placeholder="Marcus Vance" class="h-8 text-xs" /> </div> <div class="space-y-1.5"> <label ui-label class="text-xs font-semibold">Inspection Timestamp</label> <div class="border-border bg-muted/30 text-muted-foreground flex h-8 items-center rounded-md border px-3 font-mono text-xs" > {{ inspectionDate }} · {{ inspectionTime }} </div> </div> </div> <div class="border-primary/30 bg-primary/5 space-y-2 rounded-xl border p-4"> <div class="flex items-center justify-between"> <span class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="ShieldCheck" class="text-primary size-3.5" /> Live Digital Signature Stamp </span> <span class="text-primary font-mono text-xs font-medium">Verified CDL Class-A Holder</span> </div> <div class="border-primary/20 border-b pt-1 pb-2"> <p class="text-primary text-2xl font-medium tracking-wide italic"> {{ typedSignature() || 'Marcus Vance' }} </p> </div> <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-2 font-mono text-xs"> <span>Driver: {{ driverName }} · CDL #{{ driverCdl }}</span> <span>Carrier: {{ carrierName }} ({{ dotNumber }})</span> </div> </div> </div> <div ui-card-footer class="border-border bg-muted/15 flex flex-wrap items-center justify-between gap-3 border-t px-6 py-4" > <div class="text-muted-foreground text-xs"> Audit Status: <strong class="text-foreground" >{{ passCount() }} Pass, {{ failCount() }} Fail, {{ naCount() }} N/A</strong > </div> <div class="flex flex-wrap items-center gap-2"> <button type="button" ui-button variant="outline" size="sm" class="text-xs font-medium" (click)="handleReset()" > Reset Audit </button> <button type="button" ui-button size="sm" class="gap-1.5 text-xs font-medium" [disabled]="!certified() || !typedSignature().trim()" (click)="handleSubmit()" > <lucide-icon [img]="FileCheck" class="size-4" aria-hidden="true" /> Submit Completed DVIR </button> </div> </div> </div> </div> } </div> `, }) export class UiDriverInspectionChecklistComponent { protected readonly AlertCircle = AlertCircle protected readonly AlertTriangle = AlertTriangle protected readonly Camera = Camera protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly Disc = Disc protected readonly Download = Download protected readonly FileCheck = FileCheck protected readonly FileText = FileText protected readonly Flame = Flame protected readonly Gauge = Gauge protected readonly Layers = Layers protected readonly Lightbulb = Lightbulb protected readonly Minus = Minus protected readonly Printer = Printer protected readonly RotateCcw = RotateCcw protected readonly ShieldAlert = ShieldAlert protected readonly ShieldCheck = ShieldCheck protected readonly Trash2 = Trash2 protected readonly Truck = Truck protected readonly UploadCloud = UploadCloud protected readonly User = User protected readonly Wrench = Wrench protected readonly X = X protected readonly reportId = 'DVIR-2026-90412' protected readonly carrierName = 'Apex Freight Logistics Inc.' protected readonly dotNumber = 'USDOT #2940182' protected readonly inspectionDate = '2026-08-21' protected readonly inspectionTime = '06:45 AM EDT' protected readonly location = 'Terminal #12 · Ontario Logistics Hub, CA' @Input() vehicleId = 'Truck #104 · Freightliner Cascadia' @Input() trailerId = "Trailer #TR-8821 (53' Dry Van)" @Input() odometer = '142,850 miles' @Input() driverName = 'Marcus Vance' @Input() driverCdl = 'CA-948201' @Input() set initialInspectionType(v: InspectionType) { this.inspectionType.set(v) } @Input() set initialDefectState(v: boolean) { this.applyDefectSeed(!!v) } @Input() set initialSubmitted(v: boolean) { this.submitted.set(!!v) } @Input('class') className?: string @Output() submit = new EventEmitter<DVIRReportData>() @Output() defectChange = new EventEmitter<InspectionItem[]>() readonly inspectionType = signal<InspectionType>('pre-trip') readonly categories = signal<InspectionCategory[]>( DEFAULT_CATEGORIES.map((cat) => ({ ...cat, items: cat.items.map((item) => ({ ...item })) })), ) readonly defectSeverity = signal<DefectSeverity>('minor') readonly defectNotes = signal('') readonly defectReportedToDesk = signal(true) readonly defectPhotos = signal<DefectPhoto[]>([]) readonly certified = signal(true) readonly typedSignature = signal('Marcus Vance') readonly submitted = signal(false) private photoCounter = 0 constructor() { this.typedSignature.set(this.driverName) } readonly allItems = computed(() => this.categories().flatMap((c) => c.items)) readonly totalCount = computed(() => this.allItems().length) readonly passCount = computed(() => this.allItems().filter((i) => i.status === 'pass').length) readonly failCount = computed(() => this.allItems().filter((i) => i.status === 'fail').length) readonly naCount = computed(() => this.allItems().filter((i) => i.status === 'na').length) readonly failedItems = computed(() => this.allItems().filter((i) => i.status === 'fail')) readonly hasDefects = computed(() => this.failedItems().length > 0) readonly completionPercentage = computed(() => { const total = this.totalCount() return total > 0 ? Math.round((this.allItems().filter((i) => i.status !== undefined).length / total) * 100) : 0 }) get rootClass(): string { return cn('text-foreground mx-auto w-full max-w-5xl space-y-6', this.className) } get progressBarClass(): string { return cn('h-full transition-[width] duration-300', this.hasDefects() ? 'bg-warning' : 'bg-primary') } get receiptBannerClass(): string { return cn( 'border-b p-6 text-center sm:p-8', this.hasDefects() ? 'border-warning/30 bg-warning/10' : 'border-success/30 bg-success/10', ) } get receiptGlowClass(): string { return cn('absolute inset-0 rounded-full blur-xl', this.hasDefects() ? 'bg-warning/30' : 'bg-success/30') } get receiptIconWrapClass(): string { return cn( 'relative flex size-16 items-center justify-center rounded-full border shadow-xs', this.hasDefects() ? 'border-warning/40 bg-warning/20 text-warning' : 'border-success/40 bg-success/20 text-success', ) } get receiptBadgeClass(): string { return cn( 'mb-2 font-mono text-xs tracking-wider uppercase', !this.hasDefects() && 'border-success/30 bg-success/10 text-success', ) } get receiptFailClass(): string { return cn('text-lg font-bold tabular-nums', this.failCount() > 0 ? 'text-destructive' : 'text-muted-foreground') } itemRowClass(status: CheckStatus): string { return cn( 'flex flex-col gap-3 p-4 transition-colors sm:flex-row sm:items-center sm:justify-between', status === 'fail' ? 'bg-destructive/5' : 'hover:bg-muted/30', ) } segmentClass(current: CheckStatus, kind: CheckStatus): string { const base = 'focus-visible:ring-ring inline-flex cursor-pointer items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium shadow-xs transition-colors outline-none focus-visible:ring-2' if (current === kind) { if (kind === 'pass') return cn(base, 'border-success/40 bg-success/15 text-success font-semibold') if (kind === 'fail') return cn(base, 'border-destructive/40 bg-destructive/15 text-destructive font-semibold') return cn(base, 'border-border bg-muted text-foreground font-semibold') } return cn(base, 'border-border bg-background text-muted-foreground hover:bg-muted hover:text-foreground') } setInspectionType(value: string): void { this.inspectionType.set(value as InspectionType) } setDefectSeverity(value: string): void { this.defectSeverity.set(value as DefectSeverity) } private applyDefectSeed(on: boolean): void { this.categories.set( DEFAULT_CATEGORIES.map((cat) => ({ ...cat, items: cat.items.map((item) => { if (on && (item.id === 'brakes-airlines' || item.id === 'tires-pressure')) { return { ...item, status: 'fail' as CheckStatus } } return { ...item } }), })), ) this.defectSeverity.set(on ? 'critical' : 'minor') this.defectNotes.set(on ? DEFECT_SEED_NOTE : '') this.defectPhotos.set( on ? [ { id: 'p1', name: 'gladhand_seal_wear.jpg', size: '2.4 MB' }, { id: 'p2', name: 'air_pressure_gauge_105psi.png', size: '1.8 MB' }, ] : [], ) } setItemStatus(categoryId: string, itemId: string, status: CheckStatus): void { this.categories.update((prev) => prev.map((cat) => { if (cat.id !== categoryId) return cat return { ...cat, items: cat.items.map((item) => (item.id === itemId ? { ...item, status } : item)) } }), ) this.emitDefectChange() } setCategoryAll(categoryId: string, status: CheckStatus): void { this.categories.update((prev) => prev.map((cat) => { if (cat.id !== categoryId) return cat return { ...cat, items: cat.items.map((item) => ({ ...item, status })) } }), ) this.emitDefectChange() } markAllPass(): void { this.categories.update((prev) => prev.map((cat) => ({ ...cat, items: cat.items.map((item) => ({ ...item, status: 'pass' as CheckStatus })) })), ) this.emitDefectChange() } addMockPhoto(): void { this.photoCounter += 1 const n = this.photoCounter + this.defectPhotos().length this.defectPhotos.update((prev) => [ ...prev, { id: `photo-${Date.now()}-${this.photoCounter}`, name: `defect_evidence_0${n}.jpg`, size: '1.9 MB' }, ]) } removePhoto(id: string): void { this.defectPhotos.update((prev) => prev.filter((p) => p.id !== id)) } onDropzoneKeydown(event: KeyboardEvent): void { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() this.addMockPhoto() } } private emitDefectChange(): void { this.defectChange.emit(this.failedItems()) } handleSubmit(): void { if (!this.certified() || this.typedSignature().trim() === '') return this.submitted.set(true) this.submit.emit({ reportId: this.reportId, vehicleId: this.vehicleId, trailerId: this.trailerId, odometer: this.odometer, inspectionType: this.inspectionType(), driverName: this.driverName, driverCdl: this.driverCdl, carrierName: this.carrierName, dotNumber: this.dotNumber, inspectionDate: this.inspectionDate, inspectionTime: this.inspectionTime, location: this.location, categories: this.categories(), defectSeverity: this.defectSeverity(), defectNotes: this.defectNotes(), defectPhotos: this.defectPhotos(), certified: this.certified(), signatureTimestamp: new Date().toISOString(), }) } handleReset(): void { this.submitted.set(false) this.markAllPass() this.defectNotes.set('') this.defectPhotos.set([]) } }src/app/components/blocks/driver-inspection-checklist/index.ts0.3 kB
export { UiDriverInspectionChecklistComponent, type CheckStatus, type DVIRReportData, type DefectPhoto, type DefectSeverity, type InspectionCategory, type InspectionItem, type InspectionType, } from './driver-inspection-checklist.component'
Raw manifest:https://uipkge.dev/r/angular/driver-inspection-checklist.json