Menu

Change language

GitHub repo

Field Inspection Manager

blocklogistics

Mobile/Desktop commercial property & equipment site inspection audit manager featuring GPS geostamp telemetry, 4 KPI metric cards, searchable audits table with pass/fail checklist scores, photo evidence thumbnail gallery with interactive lightbox modal, cryptographic sign-off badges, and work order dispatch workflows.

Installation

$npx uipkge-ng@latest add field-inspection-manager
Manifest:https://uipkge.dev/r/angular/field-inspection-manager.jsonInstalls to:src/app/components/blocks/field-inspection-manager/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

PhotoEvidence
interface PhotoEvidence {
  id: string
  title: string
  caption: string
  time: string
  previewType:
    | 'gauge'
    | 'coil'
    | 'mount'
    | 'thermal'
    | 'box'
    | 'conduit'
    | 'riser'
    | 'pump'
    | 'switch'
    | 'tank'
    | 'battery'
    | 'ats'
    | 'spall'
    | 'rebar'
    | 'crack'
}
ChecklistItem
interface ChecklistItem {
  id: string
  label: string
  status: 'pass' | 'fail' | 'minor'
  value: string
}
InspectionRecord
interface InspectionRecord {
  id: string
  assetId: string
  assetName: string
  category: string
  facility: string
  buildingBadge: string
  date: string
  time: string
  gps: string
  gpsAccuracy: string
  scorePassed: number
  scoreTotal: number
  status: InspectionStatus
  statusLabel: string
  duration: string
  defectSummary?: string
  workOrder?: string
  photos: PhotoEvidence[]
  inspector: {
    name: string
    role: string
    initials: string
    badge: string
    signoffStatus: string
    signoffHash: string
  }
  checklist: ChecklistItem[]
}

Files installed (2)

  • src/app/components/blocks/field-inspection-manager/field-inspection-manager.component.ts75 kB
    // Mobile/Desktop commercial property & equipment site inspection audit manager:
    // GPS geostamp telemetry, 4 KPI metric cards, searchable audits table with pass/fail
    // checklist scores, photo evidence thumbnail gallery with interactive lightbox modal,
    // cryptographic sign-off badges, and work order dispatch workflows.
    // Port of the React block 1:1 — same five records, same copy, same filters.
    import { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core'
    import { NgTemplateOutlet } from '@angular/common'
    import {
      AlertCircle,
      AlertTriangle,
      Building2,
      Calendar,
      Camera,
      Check,
      CheckCircle2,
      ClipboardCheck,
      Clock,
      Copy,
      Download,
      Eye,
      LucideAngularModule,
      MapPin,
      MoreHorizontal,
      Percent,
      Plus,
      Search,
      ShieldAlert,
      ShieldCheck,
      UploadCloud,
      Wrench,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent, UiCardContentComponent, UiCardHeaderComponent } from '@/ui/card/card.component'
    import {
      UiDialogComponent,
      UiDialogContentComponent,
      UiDialogDescriptionComponent,
      UiDialogFooterComponent,
      UiDialogHeaderComponent,
      UiDialogTitleComponent,
    } from '@/ui/dialog/dialog.component'
    import {
      UiDropdownMenuComponent,
      UiDropdownMenuContentComponent,
      UiDropdownMenuItemComponent,
      UiDropdownMenuLabelComponent,
      UiDropdownMenuSeparatorComponent,
      UiDropdownMenuTriggerComponent,
    } from '@/ui/dropdown-menu/dropdown-menu.component'
    import { UiInputComponent } from '@/ui/input/input.component'
    import {
      UiSelectComponent,
      UiSelectContentComponent,
      UiSelectItemComponent,
      UiSelectTriggerComponent,
      UiSelectValueComponent,
    } from '@/ui/select/select.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export type InspectionStatus = 'passed' | 'critical' | 'minor'
    export type InspectionFilter = 'all' | 'passed' | 'critical' | 'minor'
    
    export interface PhotoEvidence {
      id: string
      title: string
      caption: string
      time: string
      previewType:
        | 'gauge'
        | 'coil'
        | 'mount'
        | 'thermal'
        | 'box'
        | 'conduit'
        | 'riser'
        | 'pump'
        | 'switch'
        | 'tank'
        | 'battery'
        | 'ats'
        | 'spall'
        | 'rebar'
        | 'crack'
    }
    
    export interface ChecklistItem {
      id: string
      label: string
      status: 'pass' | 'fail' | 'minor'
      value: string
    }
    
    export interface InspectionRecord {
      id: string
      assetId: string
      assetName: string
      category: string
      facility: string
      buildingBadge: string
      date: string
      time: string
      gps: string
      gpsAccuracy: string
      scorePassed: number
      scoreTotal: number
      status: InspectionStatus
      statusLabel: string
      duration: string
      defectSummary?: string
      workOrder?: string
      photos: PhotoEvidence[]
      inspector: {
        name: string
        role: string
        initials: string
        badge: string
        signoffStatus: string
        signoffHash: string
      }
      checklist: ChecklistItem[]
    }
    
    const defaultRecords: InspectionRecord[] = [
      {
        id: 'insp-1',
        assetId: 'AST-HVAC-4091',
        assetName: 'Commercial HVAC Chiller Unit #4',
        category: 'HVAC & Thermal Systems',
        facility: 'Building A · Rooftop Mechanical Room',
        buildingBadge: 'Bldg A',
        date: 'Aug 21, 2026',
        time: '09:15 AM EDT',
        gps: '34.0522°N, 118.2437°W',
        gpsAccuracy: '±2.4m',
        scorePassed: 18,
        scoreTotal: 18,
        status: 'passed',
        statusLabel: '18 / 18 Passed',
        duration: '26 mins',
        photos: [
          {
            id: 'p1-1',
            title: 'Compressor Suction Manifold',
            caption: 'Suction pressure holding steady at 68.4 PSI; temperature sensor verified within calibration spec.',
            time: '09:18 AM',
            previewType: 'gauge',
          },
          {
            id: 'p1-2',
            title: 'Condenser Fan Coil Assembly',
            caption: 'Clean aluminum fin surfaces with zero debris obstruction; axial fan bearings lubricated.',
            time: '09:24 AM',
            previewType: 'coil',
          },
          {
            id: 'p1-3',
            title: 'Vibration Dampener Isolators',
            caption: 'Neoprene spring isolator pads intact with zero hairline settling fractures or mounting deflection.',
            time: '09:32 AM',
            previewType: 'mount',
          },
        ],
        inspector: {
          name: 'Marcus Vance',
          role: 'Senior Field Engineer',
          initials: 'MV',
          badge: 'PE #84920-CA',
          signoffStatus: 'Verified Cryptographic Sign-off',
          signoffHash: 'SHA-256: 9f8e21...41a2',
        },
        checklist: [
          {
            id: 'c1-1',
            label: 'Refrigerant Charge & Pressures',
            status: 'pass',
            value: '68.4 PSI Suction / 225 PSI Discharge',
          },
          { id: 'c1-2', label: 'Compressor Motor Amp Draw', status: 'pass', value: '42.1A (rated 46A max)' },
          { id: 'c1-3', label: 'Chilled Water Loop Delta-T', status: 'pass', value: '10.2°F Temperature Drop' },
          { id: 'c1-4', label: 'Emergency High-Pressure Cutout', status: 'pass', value: 'Trips accurately at 380 PSI' },
        ],
      },
      {
        id: 'insp-2',
        assetId: 'AST-SOLAR-1082',
        assetName: 'Rooftop Solar Array & Inverters',
        category: 'Renewable Energy & Power',
        facility: 'Building C · South Wing Solar Deck',
        buildingBadge: 'Bldg C',
        date: 'Aug 21, 2026',
        time: '10:45 AM EDT',
        gps: '34.0528°N, 118.2445°W',
        gpsAccuracy: '±1.8m',
        scorePassed: 14,
        scoreTotal: 18,
        status: 'critical',
        statusLabel: '14 / 18 - 2 Critical Fails',
        duration: '34 mins',
        defectSummary: 'DC string combiner box busbar overheating (84.2°C); micro-inverter #6 circuit failure.',
        workOrder: 'WO-SOLAR-4921 (High Priority Dispatched)',
        photos: [
          {
            id: 'p2-1',
            title: 'Inverter #6 Thermal Hotspot',
            caption: 'FLIR radiometric thermography shows 84.2°C localized hotspot at DC-AC bridge capacitor.',
            time: '10:52 AM',
            previewType: 'thermal',
          },
          {
            id: 'p2-2',
            title: 'Combiner Box Arc Degradation',
            caption: 'Busbar contact surface oxidation with heat discoloration and slight terminal pitting.',
            time: '11:04 AM',
            previewType: 'box',
          },
          {
            id: 'p2-3',
            title: 'PV String #4 Conduit Ingress',
            caption: 'Degraded rubber gasket on string junction conduit fitting allowing moisture penetration.',
            time: '11:12 AM',
            previewType: 'conduit',
          },
        ],
        inspector: {
          name: 'Marcus Vance',
          role: 'Senior Field Engineer',
          initials: 'MV',
          badge: 'PE #84920-CA',
          signoffStatus: 'Critical Defect Action Required',
          signoffHash: 'SHA-256: 4c3d88...71ef',
        },
        checklist: [
          {
            id: 'c2-1',
            label: 'Combiner Box Busbar Temp',
            status: 'fail',
            value: '84.2°C (exceeds 65°C safe operating limit)',
          },
          { id: 'c2-2', label: 'Micro-inverter #6 Communication', status: 'fail', value: 'Modbus timeout / Offline state' },
          { id: 'c2-3', label: 'PV Panel Surface Clarity', status: 'pass', value: 'Soiling index < 1.8%' },
          { id: 'c2-4', label: 'Rapid Shutdown System', status: 'pass', value: 'Voltages drop < 30V in 12s' },
        ],
      },
      {
        id: 'insp-3',
        assetId: 'AST-FIRE-8820',
        assetName: 'Fire Suppression & Sprinkler System',
        category: 'Life Safety & Protection',
        facility: 'Main Tower · Sub-Basement Pump Room B2',
        buildingBadge: 'Tower B2',
        date: 'Aug 21, 2026',
        time: '11:30 AM EDT',
        gps: '34.0515°N, 118.2420°W',
        gpsAccuracy: '±3.1m',
        scorePassed: 17,
        scoreTotal: 18,
        status: 'minor',
        statusLabel: '17 / 18 - 1 Minor Defect',
        duration: '22 mins',
        defectSummary: 'Main riser pressure gauge annual NIST calibration tag expired by 21 days; pressure intact.',
        workOrder: 'WO-FIRE-8820 (Standard Calibration Scheduled)',
        photos: [
          {
            id: 'p3-1',
            title: 'Main Riser Pressure Gauge',
            caption: 'Static pressure 145 PSI (nominal). Calibration tag stamped 07/2025 requiring routine re-tagging.',
            time: '11:34 AM',
            previewType: 'riser',
          },
          {
            id: 'p3-2',
            title: 'Jockey Pump Shaft Packing',
            caption: 'Packing gland seal dry with zero leakage; auto start/stop pressure cutoff cycle verified.',
            time: '11:41 AM',
            previewType: 'pump',
          },
          {
            id: 'p3-3',
            title: 'OS&Y Tamper Supervisory Switch',
            caption: 'Zone 1 control valve open; tamper switch sends instant supervisory signal to FACP.',
            time: '11:48 AM',
            previewType: 'switch',
          },
        ],
        inspector: {
          name: 'Marcus Vance',
          role: 'Senior Field Engineer',
          initials: 'MV',
          badge: 'PE #84920-CA',
          signoffStatus: 'Verified Cryptographic Sign-off',
          signoffHash: 'SHA-256: 7d1a99...33bc',
        },
        checklist: [
          { id: 'c3-1', label: 'System Static Water Pressure', status: 'pass', value: '145 PSI static / 125 PSI residual' },
          {
            id: 'c3-2',
            label: 'Riser Gauge Calibration Tag',
            status: 'minor',
            value: 'Overdue by 21 days (recalibration due)',
          },
          { id: 'c3-3', label: 'Diesel Fire Pump Auto-Start', status: 'pass', value: 'Cranked to 1750 RPM in 4.2s' },
          { id: 'c3-4', label: 'Flow Alarm Switch Delay', status: 'pass', value: 'Gong chime triggered at 32s' },
        ],
      },
      {
        id: 'insp-4',
        assetId: 'AST-GEN-3304',
        assetName: 'Emergency Backup Generator #2',
        category: 'Emergency Standby Power',
        facility: 'East Campus · Utility Pad North',
        buildingBadge: 'East Pad',
        date: 'Aug 21, 2026',
        time: '01:15 PM EDT',
        gps: '34.0535°N, 118.2450°W',
        gpsAccuracy: '±2.1m',
        scorePassed: 18,
        scoreTotal: 18,
        status: 'passed',
        statusLabel: '18 / 18 Passed',
        duration: '29 mins',
        photos: [
          {
            id: 'p4-1',
            title: 'Diesel Sub-Base Day Tank',
            caption:
              'Ultra-low sulfur diesel fuel level at 98.4%; optical fuel water separator clean with zero particulate.',
            time: '01:21 PM',
            previewType: 'tank',
          },
          {
            id: 'p4-2',
            title: '24V Dual Starter Battery Bank',
            caption: 'Terminal posts clean with dielectric grease coating; float charging verified at 27.6V.',
            time: '01:28 PM',
            previewType: 'battery',
          },
          {
            id: 'p4-3',
            title: '800A ATS Transfer Switch',
            caption: 'Phase alignment and mechanical interlocking verified; utility power sync nominal.',
            time: '01:38 PM',
            previewType: 'ats',
          },
        ],
        inspector: {
          name: 'Marcus Vance',
          role: 'Senior Field Engineer',
          initials: 'MV',
          badge: 'PE #84920-CA',
          signoffStatus: 'Verified Cryptographic Sign-off',
          signoffHash: 'SHA-256: 3a7b54...82e9',
        },
        checklist: [
          { id: 'c4-1', label: 'Engine Crank & Run Test', status: 'pass', value: '60 Hz output reached in 6.8s' },
          { id: 'c4-2', label: 'Coolant Jacket Heater Temp', status: 'pass', value: '118°F Block temperature' },
          { id: 'c4-3', label: 'Oil Pressure & Level', status: 'pass', value: '55 PSI running oil pressure' },
          { id: 'c4-4', label: 'Exhaust Silencer & Insulation', status: 'pass', value: 'Zero exhaust gas or soot leakage' },
        ],
      },
      {
        id: 'insp-5',
        assetId: 'AST-STR-0019',
        assetName: 'Structural Foundation & Load Beams',
        category: 'Structural Integrity & Civil',
        facility: 'Parking Structure · Level P3 Pillar Grid 4D',
        buildingBadge: 'P3 Grid 4D',
        date: 'Aug 21, 2026',
        time: '02:40 PM EDT',
        gps: '34.0510°N, 118.2412°W',
        gpsAccuracy: '±1.9m',
        scorePassed: 16,
        scoreTotal: 18,
        status: 'critical',
        statusLabel: '16 / 18 - 2 Critical Fails',
        duration: '31 mins',
        defectSummary:
          'Concrete shear spall at beam joint 4D corbel; exposed tension rebar #6 exhibiting active oxidation.',
        workOrder: 'WO-STR-4922 (Structural Engineer On-Site)',
        photos: [
          {
            id: 'p5-1',
            title: 'Pillar 4D Beam Shear Spall',
            caption: 'Concrete shear delamination (18cm wide x 4cm depth) near primary corbel support bracket.',
            time: '02:46 PM',
            previewType: 'spall',
          },
          {
            id: 'p5-2',
            title: 'Exposed Steel Rebar Oxidation',
            caption: 'Deformed rebar #6 exposed to ambient moisture with active ferric surface oxidation.',
            time: '02:54 PM',
            previewType: 'rebar',
          },
          {
            id: 'p5-3',
            title: 'Optical Crack Comparator Test',
            caption: 'Structural hairline fissure width measured at 2.2mm across tension face (spec max 0.3mm).',
            time: '03:02 PM',
            previewType: 'crack',
          },
        ],
        inspector: {
          name: 'Marcus Vance',
          role: 'Senior Field Engineer',
          initials: 'MV',
          badge: 'PE #84920-CA',
          signoffStatus: 'Critical Defect Action Required',
          signoffHash: 'SHA-256: 8e5f12...04db',
        },
        checklist: [
          { id: 'c5-1', label: 'Concrete Cover Spalling', status: 'fail', value: '18cm spall at corbel connection' },
          {
            id: 'c5-2',
            label: 'Structural Crack Aperture',
            status: 'fail',
            value: '2.2mm fissure (exceeds 0.3mm tolerance)',
          },
          { id: 'c5-3', label: 'Expansion Joint Elastomer', status: 'pass', value: 'Sealant elasticity within spec' },
          { id: 'c5-4', label: 'Drainage Channel Clearance', status: 'pass', value: 'Deck scuppers free of sediment' },
        ],
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-field-inspection-manager, [ui-field-inspection-manager]',
      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,
        NgTemplateOutlet,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardHeaderComponent,
        UiDialogComponent,
        UiDialogContentComponent,
        UiDialogDescriptionComponent,
        UiDialogFooterComponent,
        UiDialogHeaderComponent,
        UiDialogTitleComponent,
        UiDropdownMenuComponent,
        UiDropdownMenuContentComponent,
        UiDropdownMenuItemComponent,
        UiDropdownMenuLabelComponent,
        UiDropdownMenuSeparatorComponent,
        UiDropdownMenuTriggerComponent,
        UiInputComponent,
        UiSelectComponent,
        UiSelectContentComponent,
        UiSelectItemComponent,
        UiSelectTriggerComponent,
        UiSelectValueComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="field-inspection-manager" [class]="rootClass">
          <!-- TOAST NOTIFICATION BANNER -->
          @if (toastMessage()) {
            <div
              class="border-primary/30 bg-primary/10 text-foreground fixed top-4 right-4 z-50 flex items-center gap-2 rounded-lg border px-4 py-2.5 text-xs font-medium shadow-lg backdrop-blur-md"
            >
              <lucide-icon [img]="CheckCircle2" class="text-primary size-4 shrink-0" />
              <span>{{ toastMessage() }}</span>
            </div>
          }
    
          <!-- MAIN HEADER: TITLE, INSPECTOR BADGE, ON-TIME STATUS, ACTION CTA -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-header class="flex flex-col gap-4 pb-6 lg:flex-row lg:items-start lg:justify-between">
              <div class="space-y-2">
                <!-- Compliance Pills & Live Status -->
                <div class="flex flex-wrap items-center gap-2">
                  <span ui-badge variant="outline" class="gap-1.5 font-mono text-xs">
                    <lucide-icon [img]="ClipboardCheck" class="text-primary size-3.5" aria-hidden="true" />
                    ISO-55001 & OSHA Audit Matrix
                  </span>
    
                  <!-- Emerald Status Badge -->
                  <div
                    class="border-success/20 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium"
                  >
                    <span class="relative flex size-1.5">
                      <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span>
                      <span class="bg-success relative inline-flex size-1.5 rounded-full"></span>
                    </span>
                    8 Audits Completed Today · 100% On-Time
                  </div>
                </div>
    
                <!-- Title & Inspector Subtext -->
                <div>
                  <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                    Site & Asset Inspection Audits
                  </h1>
                  <div class="text-muted-foreground mt-1 flex flex-wrap items-center gap-2 text-xs sm:text-sm">
                    <div class="text-foreground flex items-center gap-1.5 font-medium">
                      <ui-avatar class="border-border size-5 border">
                        <ui-avatar-fallback class="bg-primary/10 text-primary text-xs font-bold">MV</ui-avatar-fallback>
                      </ui-avatar>
                      <span>Marcus Vance</span>
                    </div>
                    <span class="text-muted-foreground">Senior Field Engineer (PE #84920-CA)</span>
                    <span class="text-muted-foreground font-mono">Terminal: Apex Facility West</span>
                  </div>
                </div>
              </div>
    
              <!-- Action CTAs -->
              <div class="flex flex-wrap items-center gap-2.5 pt-1">
                <button
                  ui-button
                  aria-label="Download attachment"
                  variant="outline"
                  size="sm"
                  class="gap-1.5 text-xs font-medium shadow-xs"
                  (click)="showToast('Exporting complete site inspection audit summary CSV...')"
                >
                  <lucide-icon [img]="Download" class="size-3.5" aria-hidden="true" />
                  Export Audits CSV
                </button>
    
                <button
                  ui-button
                  size="sm"
                  class="gap-1.5 text-xs font-medium shadow-xs"
                  (click)="handleStartNewInspection()"
                >
                  <lucide-icon [img]="Plus" class="size-3.5" aria-hidden="true" />
                  Start New Inspection
                </button>
              </div>
            </div>
          </div>
    
          <!-- 4 FIELD INSPECTION METRIC KPI CARDS -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4 sm:p-5">
                <div class="flex items-center justify-between">
                  <span class="text-muted-foreground text-xs font-medium">Completed Inspections</span>
                  <div
                    class="border-success/20 bg-success/10 text-success flex size-8 items-center justify-center rounded-md border"
                  >
                    <lucide-icon [img]="CheckCircle2" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-3">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">24</div>
                  <div class="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
                    <span class="text-success font-medium">24 Audits This Week</span>
                    <span>·</span>
                    <span class="text-muted-foreground">+14.2% vs target</span>
                  </div>
                </div>
              </div>
            </div>
    
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4 sm:p-5">
                <div class="flex items-center justify-between">
                  <span class="text-muted-foreground text-xs font-medium">Critical Defects Flagged</span>
                  <div
                    class="border-destructive/20 bg-destructive/10 text-destructive flex size-8 items-center justify-center rounded-md border"
                  >
                    <lucide-icon [img]="ShieldAlert" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-3">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">2</div>
                  <div class="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
                    <span class="text-destructive font-semibold">2 Critical Deficiencies</span>
                    <span>·</span>
                    <span class="text-muted-foreground">Work Orders Dispatched</span>
                  </div>
                </div>
              </div>
            </div>
    
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4 sm:p-5">
                <div class="flex items-center justify-between">
                  <span class="text-muted-foreground text-xs font-medium">First-Time Pass Rate</span>
                  <div
                    class="border-primary/20 bg-primary/10 text-primary flex size-8 items-center justify-center rounded-md border"
                  >
                    <lucide-icon [img]="Percent" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-3">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">91.6%</div>
                  <div class="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
                    <span class="text-success font-medium">91.6% Pass Rate</span>
                    <span>·</span>
                    <span class="text-muted-foreground">Above 90.0% SLA</span>
                  </div>
                </div>
              </div>
            </div>
    
            <div ui-card class="border-border shadow-xs">
              <div ui-card-content class="p-4 sm:p-5">
                <div class="flex items-center justify-between">
                  <span class="text-muted-foreground text-xs font-medium">Avg Inspection Duration</span>
                  <div
                    class="border-warning/20 bg-warning/10 text-warning flex size-8 items-center justify-center rounded-md border"
                  >
                    <lucide-icon [img]="Clock" class="size-4" aria-hidden="true" />
                  </div>
                </div>
                <div class="mt-3">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">28m</div>
                  <div class="text-muted-foreground mt-1 flex items-center gap-1.5 text-xs">
                    <span class="text-foreground font-medium">28 mins / audit</span>
                    <span>·</span>
                    <span class="text-success">-4m vs benchmark</span>
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <!-- SEARCH & FILTER TOOLBAR -->
          <div ui-card class="border-border shadow-xs">
            <div ui-card-content class="p-4">
              <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                <!-- Filter Tabs / Pills -->
                <div class="flex flex-wrap items-center gap-1.5">
                  <button type="button" [class]="filterPillClass('all')" (click)="activeFilter.set('all')">
                    All Audits (5)
                  </button>
                  <button type="button" [class]="filterPillClass('passed')" (click)="activeFilter.set('passed')">
                    <lucide-icon [img]="CheckCircle2" class="size-3.5" />
                    Passed (2)
                  </button>
                  <button type="button" [class]="filterPillClass('critical')" (click)="activeFilter.set('critical')">
                    <lucide-icon [img]="AlertTriangle" class="size-3.5" />
                    Critical Defects (2)
                  </button>
                  <button type="button" [class]="filterPillClass('minor')" (click)="activeFilter.set('minor')">
                    <lucide-icon [img]="AlertCircle" class="size-3.5" />
                    Minor Issues (1)
                  </button>
                </div>
    
                <!-- Search Input & Facility Selector -->
                <div class="flex flex-wrap items-center gap-2">
                  <div class="w-full sm:w-48">
                    <ui-select [value]="selectedFacility()" (valueChange)="setSelectedFacility($event)">
                      <button ui-select-trigger class="h-8 text-xs">
                        <ui-select-value placeholder="All Facilities" />
                      </button>
                      <ui-select-content>
                        <ui-select-item value="all">All Facilities (5)</ui-select-item>
                        <ui-select-item value="Building A">Building A (2)</ui-select-item>
                        <ui-select-item value="Building B">Building B (1)</ui-select-item>
                        <ui-select-item value="Building C">Building C (1)</ui-select-item>
                        <ui-select-item value="Parking Structure">Parking Structure (1)</ui-select-item>
                      </ui-select-content>
                    </ui-select>
                  </div>
    
                  <div class="relative w-full sm:w-64">
                    <lucide-icon
                      [img]="Search"
                      class="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2"
                    />
                    <ui-input
                      [value]="searchQuery()"
                      (valueChange)="searchQuery.set($event)"
                      type="text"
                      placeholder="Search asset, ID, category..."
                      class="h-8 pl-8 text-xs"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>
    
          <!-- AUDIT RECORDS DATA TABLE -->
          <div ui-card class="border-border overflow-hidden shadow-xs">
            <div class="overflow-x-auto">
              <table ui-table>
                <thead ui-table-header>
                  <tr ui-table-row class="bg-muted/30">
                    <th ui-table-head class="text-foreground w-[220px] text-xs font-semibold">Asset & Facility</th>
                    <th ui-table-head class="text-foreground text-xs font-semibold">Date & GPS Stamping</th>
                    <th ui-table-head class="text-foreground text-xs font-semibold">Inspection Checklist</th>
                    <th ui-table-head class="text-foreground text-xs font-semibold">Status & Work Order</th>
                    <th ui-table-head class="text-foreground text-xs font-semibold">Photographic Evidence</th>
                    <th ui-table-head class="text-foreground text-xs font-semibold">Inspector & Sign-off</th>
                    <th ui-table-head class="text-foreground text-right text-xs font-semibold">Actions</th>
                  </tr>
                </thead>
                <tbody ui-table-body>
                  @for (record of filteredRecords(); track record.id) {
                    <tr ui-table-row class="border-border hover:bg-muted/20 transition-colors">
                      <!-- 1. Asset & Facility -->
                      <td ui-table-cell class="py-3.5 align-top">
                        <div class="space-y-1">
                          <div class="flex items-center gap-1.5">
                            <span class="text-foreground text-xs font-bold">{{ record.assetName }}</span>
                            <span ui-badge variant="outline" class="font-mono text-xs">{{ record.buildingBadge }}</span>
                          </div>
                          <div class="text-muted-foreground font-mono text-xs">{{ record.assetId }}</div>
                          <div class="text-muted-foreground/90 flex items-center gap-1 text-xs">
                            <lucide-icon [img]="Building2" class="size-3 shrink-0" />
                            <span class="truncate">{{ record.facility }}</span>
                          </div>
                        </div>
                      </td>
    
                      <!-- 2. Date & GPS Stamping -->
                      <td ui-table-cell class="py-3.5 align-top">
                        <div class="space-y-1">
                          <div class="text-foreground flex items-center gap-1 text-xs font-medium">
                            <lucide-icon [img]="Calendar" class="text-muted-foreground size-3" />
                            <span>{{ record.date }}</span>
                          </div>
                          <div class="text-muted-foreground flex items-center gap-1 font-mono text-xs">
                            <lucide-icon [img]="Clock" class="size-3" />
                            <span>{{ record.time }} ({{ record.duration }})</span>
                          </div>
                          <button
                            type="button"
                            [attr.aria-label]="'Copy GPS coordinates for ' + record.assetId + ': ' + record.gps"
                            class="border-border hover:bg-muted focus-visible:ring-ring bg-muted/40 text-muted-foreground inline-flex cursor-pointer items-center gap-1 rounded border px-2 py-0.5 font-mono text-xs transition-colors focus-visible:ring-2 focus-visible:outline-none"
                            (click)="copyGps(record.gps, record.id)"
                          >
                            <lucide-icon [img]="MapPin" class="text-primary size-2.5" />
                            <span>{{ record.gps }}</span>
                            @if (copiedGpsId() === record.id) {
                              <lucide-icon [img]="Check" class="text-success size-2.5" />
                            } @else {
                              <lucide-icon [img]="Copy" class="text-muted-foreground/60 size-2.5" />
                            }
                          </button>
                        </div>
                      </td>
    
                      <!-- 3. Inspection Checklist -->
                      <td ui-table-cell class="py-3.5 align-top">
                        <div class="w-48 space-y-1.5">
                          <div class="flex items-center justify-between text-xs">
                            <span class="text-muted-foreground font-medium">Score</span>
                            <span [class]="scoreClass(record.status)">{{ record.statusLabel }}</span>
                          </div>
                          <div class="bg-muted h-1.5 w-full overflow-hidden rounded-full">
                            <div
                              [class]="scoreBarClass(record.status)"
                              [style.width]="(record.scorePassed / record.scoreTotal) * 100 + '%'"
                            ></div>
                          </div>
                          <div class="text-muted-foreground space-y-0.5 text-xs">
                            @for (item of record.checklist.slice(0, 2); track item.id) {
                              <div class="flex items-center gap-1 truncate font-mono text-xs">
                                @if (item.status === 'pass') {
                                  <lucide-icon [img]="CheckCircle2" class="text-success size-2.5 shrink-0" />
                                } @else if (item.status === 'fail') {
                                  <lucide-icon [img]="AlertTriangle" class="text-destructive size-2.5 shrink-0" />
                                } @else {
                                  <lucide-icon [img]="AlertCircle" class="text-warning size-2.5 shrink-0" />
                                }
                                <span class="truncate">{{ item.label }}: {{ item.value }}</span>
                              </div>
                            }
                          </div>
                        </div>
                      </td>
    
                      <!-- 4. Status & Work Order -->
                      <td ui-table-cell class="py-3.5 align-top">
                        <div class="space-y-1.5">
                          <span
                            ui-badge
                            [variant]="record.status === 'passed' ? 'default' : 'destructive'"
                            [class]="statusBadgeClass(record.status)"
                          >
                            @if (record.status === 'passed') {
                              <lucide-icon [img]="CheckCircle2" class="mr-1 size-3" />
                            } @else if (record.status === 'critical') {
                              <lucide-icon [img]="AlertTriangle" class="mr-1 size-3" />
                            } @else {
                              <lucide-icon [img]="AlertCircle" class="mr-1 size-3" />
                            }
                            {{ record.status }}
                          </span>
    
                          @if (record.defectSummary) {
                            <p class="text-destructive max-w-[200px] text-xs leading-tight font-medium">
                              {{ record.defectSummary }}
                            </p>
                          } @else {
                            <p class="text-muted-foreground text-xs leading-tight">
                              All calibration points verified within tolerance.
                            </p>
                          }
    
                          @if (record.workOrder) {
                            <div class="pt-0.5">
                              <span
                                class="border-border bg-muted/30 text-muted-foreground inline-flex items-center gap-1 rounded border px-1.5 py-0.5 font-mono text-xs"
                              >
                                <lucide-icon [img]="Wrench" class="text-warning size-2.5" />
                                <span class="max-w-[140px] truncate">{{ record.workOrder }}</span>
                              </span>
                            </div>
                          }
                        </div>
                      </td>
    
                      <!-- 5. Photographic Evidence -->
                      <td ui-table-cell class="py-3.5 align-top">
                        <div class="space-y-1.5">
                          <div class="flex items-center gap-1.5">
                            @for (photo of record.photos; track photo.id; let pIdx = $index) {
                              <button
                                type="button"
                                [attr.aria-label]="'View inspection photo ' + (pIdx + 1) + ': ' + photo.title"
                                class="border-border hover:border-primary focus-visible:ring-ring group/thumb relative size-11 shrink-0 cursor-pointer overflow-hidden rounded-md border bg-zinc-900 shadow-xs transition-colors focus-visible:ring-2 focus-visible:outline-none"
                                (click)="handleOpenPhoto(photo, record)"
                              >
                                <ng-container *ngTemplateOutlet="thumbnail; context: { $implicit: photo.previewType }" />
                                <div
                                  class="absolute inset-0 flex items-center justify-center bg-black/60 opacity-0 transition-opacity group-hover/thumb:opacity-100"
                                >
                                  <lucide-icon [img]="Eye" class="size-3.5 text-white" />
                                </div>
                              </button>
                            }
                          </div>
                          <div class="text-muted-foreground flex items-center gap-1 text-xs">
                            <lucide-icon [img]="Camera" class="size-3" />
                            <span>{{ record.photos.length }} Attachments</span>
                          </div>
                        </div>
                      </td>
    
                      <!-- 6. Inspector & Sign-off -->
                      <td ui-table-cell class="py-3.5 align-top">
                        <div class="space-y-1">
                          <div class="flex items-center gap-2">
                            <ui-avatar class="border-border size-6 border">
                              <ui-avatar-fallback class="bg-primary/10 text-primary text-xs font-bold">
                                {{ record.inspector.initials }}
                              </ui-avatar-fallback>
                            </ui-avatar>
                            <span class="text-foreground text-xs font-semibold">{{ record.inspector.name }}</span>
                          </div>
                          <div>
                            <span [class]="signoffClass(record.status)">
                              @if (record.status !== 'critical') {
                                <lucide-icon [img]="ShieldCheck" class="size-3 shrink-0" />
                              } @else {
                                <lucide-icon [img]="ShieldAlert" class="size-3 shrink-0" />
                              }
                              {{ record.inspector.signoffStatus }}
                            </span>
                          </div>
                          <div class="text-muted-foreground/80 font-mono text-xs">{{ record.inspector.signoffHash }}</div>
                        </div>
                      </td>
    
                      <!-- 7. Actions Dropdown -->
                      <td ui-table-cell class="py-3.5 text-right align-top">
                        <ui-dropdown-menu>
                          <button ui-button ui-dropdown-menu-trigger variant="ghost" size="sm" class="size-8 p-0">
                            <span class="sr-only">Open audit menu</span>
                            <lucide-icon [img]="MoreHorizontal" class="size-4" />
                          </button>
                          <ui-dropdown-menu-content align="end" class="w-48 text-xs">
                            <div ui-dropdown-menu-label class="font-mono text-xs">{{ record.assetId }}</div>
                            <ui-dropdown-menu-separator />
                            <ui-dropdown-menu-item class="cursor-pointer gap-2" (select)="handleViewReport(record)">
                              <lucide-icon [img]="Eye" class="text-primary size-3.5" />
                              <span>View Audit Report</span>
                            </ui-dropdown-menu-item>
                            <ui-dropdown-menu-item class="cursor-pointer gap-2" (select)="handleDownloadPdf(record)">
                              <lucide-icon [img]="Download" class="size-3.5" />
                              <span>Download PDF</span>
                            </ui-dropdown-menu-item>
                            <ui-dropdown-menu-item class="cursor-pointer gap-2" (select)="copyGps(record.gps, record.id)">
                              <lucide-icon [img]="Copy" class="size-3.5" />
                              <span>Copy GPS Coordinates</span>
                            </ui-dropdown-menu-item>
                            <ui-dropdown-menu-separator />
                            <ui-dropdown-menu-item
                              class="cursor-pointer gap-2"
                              [class.text-destructive]="record.status === 'critical'"
                              [class.font-semibold]="record.status === 'critical'"
                              (select)="handleCreateWorkOrder(record)"
                            >
                              <lucide-icon [img]="Wrench" class="size-3.5" />
                              <span>{{ record.status === 'critical' ? 'Expedite Work Order' : 'Create Work Order' }}</span>
                            </ui-dropdown-menu-item>
                          </ui-dropdown-menu-content>
                        </ui-dropdown-menu>
                      </td>
                    </tr>
                  }
                </tbody>
              </table>
            </div>
          </div>
    
          <!-- PHOTO EVIDENCE LIGHTBOX MODAL DIALOG -->
          <ui-dialog [open]="!!previewPhoto()" (openChange)="onPreviewOpenChange($event)">
            @if (previewPhoto(); as preview) {
              <ui-dialog-content class="sm:max-w-2xl">
                <div ui-dialog-header>
                  <div class="flex items-center justify-between pr-4">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="Camera" class="text-primary size-4" />
                      <h2 ui-dialog-title class="text-base font-bold">{{ preview.photo.title }}</h2>
                    </div>
                    <span ui-badge variant="outline" class="font-mono text-xs">{{ preview.record.assetId }}</span>
                  </div>
                  <p ui-dialog-description class="text-xs">
                    Captured during {{ preview.record.assetName }} site inspection audit.
                  </p>
                </div>
    
                <div class="space-y-4 py-2">
                  <div
                    class="border-border relative aspect-[16/10] w-full overflow-hidden rounded-lg border bg-gradient-to-br from-zinc-800 via-zinc-900 to-black p-4 text-white shadow-inner"
                  >
                    <div class="absolute inset-0 flex items-center justify-center p-6">
                      @if (preview.photo.previewType === 'gauge') {
                        <svg class="h-full w-full" viewBox="0 0 300 200" fill="none">
                          <rect width="300" height="200" fill="#18181b" />
                          <circle cx="150" cy="100" r="70" fill="#27272a" stroke="#71717a" stroke-width="4" />
                          <path
                            d="M 100,125 A 55,55 0 1,1 200,125"
                            fill="none"
                            stroke="#22c55e"
                            stroke-width="6"
                            stroke-dasharray="4 2"
                          />
                          <line
                            x1="150"
                            y1="100"
                            x2="185"
                            y2="65"
                            stroke="#ef4444"
                            stroke-width="3.5"
                            stroke-linecap="round"
                          />
                          <circle cx="150" cy="100" r="8" fill="#f4f4f5" />
                          <text x="150" y="140" fill="#a1a1aa" font-size="12" text-anchor="middle" font-family="monospace">
                            68.4 PSI · NOMINAL
                          </text>
                        </svg>
                      } @else if (preview.photo.previewType === 'thermal') {
                        <svg class="h-full w-full" viewBox="0 0 300 200" fill="none">
                          <rect width="300" height="200" fill="#3b0764" />
                          <circle cx="150" cy="100" r="65" fill="#db2777" fill-opacity="0.6" />
                          <circle cx="150" cy="100" r="30" fill="#facc15" />
                          <line x1="80" y1="100" x2="220" y2="100" stroke="#ffffff" stroke-width="1.5" />
                          <line x1="150" y1="30" x2="150" y2="170" stroke="#ffffff" stroke-width="1.5" />
                          <circle cx="150" cy="100" r="6" stroke="#ffffff" stroke-width="1.5" fill="none" />
                          <text x="160" y="90" fill="#ffffff" font-size="14" font-weight="bold" font-family="monospace">
                            84.2°C CRITICAL
                          </text>
                        </svg>
                      } @else if (preview.photo.previewType === 'spall') {
                        <svg class="h-full w-full" viewBox="0 0 300 200" fill="none">
                          <rect width="300" height="200" fill="#27272a" />
                          <polygon
                            points="40,30 260,30 260,170 170,170 130,110 40,100"
                            fill="#52525b"
                            stroke="#71717a"
                            stroke-width="2"
                          />
                          <polygon
                            points="130,110 180,90 170,170"
                            fill="#ef4444"
                            fill-opacity="0.4"
                            stroke="#ef4444"
                            stroke-width="2"
                          />
                          <line
                            x1="130"
                            y1="110"
                            x2="180"
                            y2="90"
                            stroke="#facc15"
                            stroke-width="2"
                            stroke-dasharray="3 3"
                          />
                          <text x="155" y="80" fill="#facc15" font-size="12" font-family="monospace">18cm SHEAR SPALL</text>
                        </svg>
                      } @else {
                        <svg class="h-full w-full" viewBox="0 0 300 200" fill="none">
                          <rect width="300" height="200" fill="#18181b" />
                          <rect
                            x="40"
                            y="30"
                            width="220"
                            height="140"
                            rx="4"
                            fill="#27272a"
                            stroke="#3f3f46"
                            stroke-width="2"
                          />
                          <circle cx="150" cy="100" r="35" fill="#3f3f46" stroke="#38bdf8" stroke-width="2" />
                          <line x1="70" y1="60" x2="230" y2="60" stroke="#38bdf8" stroke-width="1.5" />
                          <line x1="70" y1="140" x2="230" y2="140" stroke="#38bdf8" stroke-width="1.5" />
                        </svg>
                      }
                    </div>
    
                    <div class="pointer-events-none absolute inset-4 border border-white/20">
                      <div class="border-success absolute -top-1 -left-1 size-3 border-t-2 border-l-2"></div>
                      <div class="border-success absolute -top-1 -right-1 size-3 border-t-2 border-r-2"></div>
                      <div class="border-success absolute -bottom-1 -left-1 size-3 border-b-2 border-l-2"></div>
                      <div class="border-success absolute -right-1 -bottom-1 size-3 border-r-2 border-b-2"></div>
                    </div>
    
                    <div
                      class="absolute top-3 left-3 flex items-center gap-1.5 rounded bg-black/70 px-2 py-1 font-mono text-xs backdrop-blur-sm"
                    >
                      <span class="bg-success size-2 animate-pulse rounded-full"></span>
                      <span>GEO-AUTHENTICATED FIELD PHOTO</span>
                    </div>
    
                    <div
                      class="absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/90 via-black/60 to-transparent p-3 pt-6 font-mono text-xs"
                    >
                      <div class="flex flex-wrap items-center justify-between gap-2">
                        <span class="text-success font-bold tabular-nums">
                          📍 {{ preview.record.gps }} ({{ preview.record.gpsAccuracy }})
                        </span>
                        <span class="text-zinc-300">{{ preview.record.date }} · {{ preview.photo.time }}</span>
                      </div>
                    </div>
                  </div>
    
                  <div class="border-border bg-muted/20 space-y-2 rounded-lg border p-3 text-xs">
                    <div class="text-foreground font-semibold">Inspector Field Observation:</div>
                    <p class="text-muted-foreground leading-relaxed">{{ preview.photo.caption }}</p>
                  </div>
    
                  <div class="grid grid-cols-2 gap-2 text-xs sm:grid-cols-4">
                    <div class="border-border bg-muted/30 rounded border p-2">
                      <span class="text-muted-foreground block text-xs">Asset ID</span>
                      <span class="text-foreground font-mono font-semibold">{{ preview.record.assetId }}</span>
                    </div>
                    <div class="border-border bg-muted/30 rounded border p-2">
                      <span class="text-muted-foreground block text-xs">Facility</span>
                      <span class="text-foreground truncate font-medium">{{ preview.record.buildingBadge }}</span>
                    </div>
                    <div class="border-border bg-muted/30 rounded border p-2">
                      <span class="text-muted-foreground block text-xs">Capture Timestamp</span>
                      <span class="text-foreground font-mono font-medium tabular-nums">{{ preview.photo.time }}</span>
                    </div>
                    <div class="border-border bg-muted/30 rounded border p-2">
                      <span class="text-muted-foreground block text-xs">Cryptographic Seal</span>
                      <span class="text-success font-mono font-medium">Valid SHA-256</span>
                    </div>
                  </div>
                </div>
    
                <div ui-dialog-footer class="flex flex-wrap items-center justify-between gap-2">
                  <button
                    ui-button
                    aria-label="Download attachment"
                    variant="outline"
                    size="sm"
                    class="text-xs font-medium"
                    (click)="showToast('Exporting high-resolution raw image file...')"
                  >
                    <lucide-icon [img]="Download" class="mr-1.5 size-3.5" />
                    Download Original Photo
                  </button>
                  <button ui-button size="sm" class="text-xs" (click)="previewPhoto.set(null)">Close</button>
                </div>
              </ui-dialog-content>
            }
          </ui-dialog>
    
          <!-- COMPREHENSIVE AUDIT REPORT MODAL DIALOG -->
          <ui-dialog [open]="!!selectedAudit()" (openChange)="onAuditOpenChange($event)">
            @if (selectedAudit(); as audit) {
              <ui-dialog-content class="max-h-[90vh] overflow-y-auto sm:max-w-3xl">
                <div ui-dialog-header>
                  <div class="flex flex-wrap items-center justify-between gap-2 pr-4">
                    <div class="space-y-1">
                      <div class="flex items-center gap-2">
                        <span ui-badge variant="outline" class="font-mono text-xs">{{ audit.assetId }}</span>
                        <span
                          ui-badge
                          [variant]="
                            audit.status === 'critical' ? 'destructive' : audit.status === 'minor' ? 'warning' : 'outline'
                          "
                          [class]="
                            audit.status === 'passed'
                              ? 'border-success/30 bg-success/10 text-success font-mono text-xs font-semibold'
                              : 'font-mono text-xs font-semibold'
                          "
                        >
                          {{ audit.statusLabel }}
                        </span>
                      </div>
                      <h2 ui-dialog-title class="text-lg font-bold sm:text-xl">{{ audit.assetName }}</h2>
                    </div>
                  </div>
                  <p ui-dialog-description class="text-xs">
                    Commercial facility asset engineering inspection audit record · {{ audit.category }}
                  </p>
                </div>
    
                <div class="space-y-5 py-2">
                  <div class="grid gap-3 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">Facility Location</span>
                      <span class="text-foreground mt-0.5 block text-xs font-semibold">{{ audit.facility }}</span>
                    </div>
                    <div class="border-border bg-muted/20 rounded-lg border p-3">
                      <span class="text-muted-foreground block text-xs font-medium">Inspection Time</span>
                      <span class="text-foreground mt-0.5 block text-xs font-semibold tabular-nums">
                        {{ audit.date }} · {{ audit.time }}
                      </span>
                    </div>
                    <div class="border-border bg-muted/20 rounded-lg border p-3">
                      <span class="text-muted-foreground block text-xs font-medium">GPS Geostamp</span>
                      <span class="text-foreground mt-0.5 block font-mono text-xs font-semibold tabular-nums">
                        {{ audit.gps }}
                      </span>
                      <span class="text-muted-foreground text-xs">Accuracy: {{ audit.gpsAccuracy }}</span>
                    </div>
                    <div class="border-border bg-muted/20 rounded-lg border p-3">
                      <span class="text-muted-foreground block text-xs font-medium">Audit Duration</span>
                      <span class="text-foreground mt-0.5 block text-xs font-semibold tabular-nums">
                        {{ audit.duration }}
                      </span>
                      <span class="text-muted-foreground text-xs">SLA compliant</span>
                    </div>
                  </div>
    
                  @if (audit.status === 'critical') {
                    <div
                      class="border-destructive/30 bg-destructive/10 text-destructive space-y-1.5 rounded-lg border p-3.5 text-xs"
                    >
                      <div class="flex items-center gap-1.5 font-bold">
                        <lucide-icon [img]="AlertTriangle" class="text-destructive size-4" />
                        <span>Critical Deficiencies Flagged · Action Dispatched</span>
                      </div>
                      <p class="leading-relaxed">{{ audit.defectSummary }}</p>
                      <div class="pt-1 font-mono font-semibold">Active Work Order: {{ audit.workOrder }}</div>
                    </div>
                  }
    
                  <div class="space-y-2">
                    <div class="flex items-center justify-between">
                      <span class="text-foreground text-xs font-semibold">Pass / Fail Checklist Audit Items</span>
                      <span class="text-muted-foreground font-mono text-xs tabular-nums">
                        {{ audit.scorePassed }} / {{ audit.scoreTotal }} Passed
                      </span>
                    </div>
                    <div class="border-border divide-border divide-y rounded-lg border">
                      @for (item of audit.checklist; track item.id) {
                        <div class="flex items-center justify-between p-2.5 text-xs">
                          <div class="flex items-center gap-2">
                            <span
                              ui-badge
                              [variant]="
                                item.status === 'fail' ? 'destructive' : item.status === 'minor' ? 'warning' : 'outline'
                              "
                              class="h-5 px-1.5 font-mono text-xs uppercase"
                            >
                              {{ item.status }}
                            </span>
                            <span class="text-foreground font-medium">{{ item.label }}</span>
                          </div>
                          <span class="text-muted-foreground font-mono text-xs tabular-nums">{{ item.value }}</span>
                        </div>
                      }
                    </div>
                  </div>
    
                  <div class="space-y-2">
                    <span class="text-foreground text-xs font-semibold">Photographic Evidence Attachments</span>
                    <div class="grid grid-cols-3 gap-3">
                      @for (photo of audit.photos; track photo.id) {
                        <button
                          type="button"
                          class="border-border hover:border-primary/60 group/p cursor-pointer overflow-hidden rounded-lg border bg-zinc-900 p-2 text-left transition-colors"
                          (click)="handleOpenPhoto(photo, audit)"
                        >
                          <div class="relative aspect-[16/10] w-full overflow-hidden rounded bg-black">
                            <div class="absolute inset-0 flex items-center justify-center">
                              <lucide-icon [img]="Camera" class="text-muted-foreground size-6" />
                            </div>
                          </div>
                          <div class="text-foreground mt-1.5 truncate text-xs font-semibold">{{ photo.title }}</div>
                          <div class="text-muted-foreground font-mono text-xs tabular-nums">{{ photo.time }}</div>
                        </button>
                      }
                    </div>
                  </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 Engineer Sign-off
                      </span>
                      <span class="text-muted-foreground font-mono text-xs">{{ audit.inspector.badge }}</span>
                    </div>
                    <div class="border-border/60 border-b pt-1 pb-3">
                      <p class="text-primary text-2xl font-medium tracking-wide italic">{{ audit.inspector.name }}</p>
                    </div>
                    <div class="text-muted-foreground flex flex-wrap items-center justify-between gap-2 font-mono text-xs">
                      <span>Signer: {{ audit.inspector.name }} · {{ audit.inspector.role }}</span>
                      <span>{{ audit.inspector.signoffHash }}</span>
                    </div>
                  </div>
                </div>
    
                <div ui-dialog-footer class="flex flex-wrap items-center justify-between gap-2">
                  <button
                    ui-button
                    aria-label="Download attachment"
                    variant="outline"
                    size="sm"
                    class="text-xs font-medium"
                    (click)="handleDownloadPdf(audit)"
                  >
                    <lucide-icon [img]="Download" class="mr-1.5 size-3.5" />
                    Download PDF Report
                  </button>
                  <button ui-button size="sm" class="text-xs" (click)="selectedAudit.set(null)">Close</button>
                </div>
              </ui-dialog-content>
            }
          </ui-dialog>
    
          <!-- START NEW INSPECTION MODAL DIALOG -->
          <ui-dialog [open]="isNewAuditOpen()" (openChange)="isNewAuditOpen.set($event)">
            <ui-dialog-content class="sm:max-w-xl">
              <div ui-dialog-header>
                <div class="flex items-center gap-2">
                  <lucide-icon [img]="ClipboardCheck" class="text-primary size-5" />
                  <h2 ui-dialog-title class="text-base font-bold">Start New Site & Asset Inspection Audit</h2>
                </div>
                <p ui-dialog-description class="text-xs">
                  Initiate a field engineering inspection with automatic GPS geostamping and checklist validation.
                </p>
              </div>
    
              @if (newAuditSuccess()) {
                <div class="space-y-3 py-8 text-center">
                  <div class="bg-success/20 text-success mx-auto flex size-12 items-center justify-center rounded-full">
                    <lucide-icon [img]="CheckCircle2" class="size-6" />
                  </div>
                  <h3 class="text-foreground text-base font-bold">Inspection Audit Logged Successfully</h3>
                  <p class="text-muted-foreground mx-auto max-w-sm text-xs">
                    Audit has been recorded with GPS geostamp 34.0522°N, 118.2437°W and synced to cloud repository.
                  </p>
                </div>
              } @else {
                <div class="space-y-4 py-2">
                  <div class="grid gap-3 sm:grid-cols-2">
                    <div class="space-y-1.5">
                      <label class="text-foreground text-xs font-semibold">Select Target Asset *</label>
                      <ui-select [value]="newAuditAsset()" (valueChange)="newAuditAsset.set($event)">
                        <button ui-select-trigger class="h-8 text-xs">
                          <ui-select-value />
                        </button>
                        <ui-select-content>
                          <ui-select-item value="AST-HVAC-4091">AST-HVAC-4091 · Chiller Unit #4</ui-select-item>
                          <ui-select-item value="AST-SOLAR-1082">AST-SOLAR-1082 · Solar Array & Inverters</ui-select-item>
                          <ui-select-item value="AST-FIRE-8820">AST-FIRE-8820 · Fire Sprinkler System</ui-select-item>
                          <ui-select-item value="AST-GEN-3304">AST-GEN-3304 · Backup Generator #2</ui-select-item>
                          <ui-select-item value="AST-STR-0019">AST-STR-0019 · Foundation & Beams</ui-select-item>
                          <ui-select-item value="AST-ELEV-9012">AST-ELEV-9012 · Passenger Elevator Bank A</ui-select-item>
                        </ui-select-content>
                      </ui-select>
                    </div>
    
                    <div class="space-y-1.5">
                      <label class="text-foreground text-xs font-semibold">Facility Location</label>
                      <ui-input
                        [value]="newAuditFacility()"
                        (valueChange)="newAuditFacility.set($event)"
                        class="h-8 text-xs"
                      />
                    </div>
                  </div>
    
                  <div class="border-border bg-muted/20 flex items-center justify-between rounded-lg border p-3 text-xs">
                    <div class="flex items-center gap-2">
                      <lucide-icon [img]="MapPin" class="text-success size-4 shrink-0" />
                      <div>
                        <span class="text-foreground font-semibold">Live GPS Telemetry Lock</span>
                        <span class="text-muted-foreground block font-mono text-xs">
                          34.0522°N, 118.2437°W · Accuracy ±1.5m
                        </span>
                      </div>
                    </div>
                    <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success font-mono text-xs">
                      GPS Locked
                    </span>
                  </div>
    
                  <div class="space-y-1.5">
                    <label class="text-foreground text-xs font-semibold">Photographic Evidence</label>
                    <div
                      class="border-border/80 hover:border-primary/50 bg-card cursor-pointer rounded-lg border-2 border-dashed p-4 text-center transition-colors"
                      (click)="showToast('Mock photo uploaded: asset_evidence_live.jpg')"
                    >
                      <div class="flex flex-col items-center justify-center gap-1">
                        <lucide-icon [img]="UploadCloud" class="text-primary size-5" />
                        <p class="text-foreground text-xs font-medium">Click to upload photo evidence or drag & drop</p>
                        <p class="text-muted-foreground text-xs">Supports JPG, PNG up to 15MB with GPS EXIF retention</p>
                      </div>
                    </div>
                  </div>
    
                  <div class="space-y-1.5">
                    <label class="text-foreground text-xs font-semibold">Inspector Observations & Checklist Notes</label>
                    <ui-input
                      [value]="newAuditNotes()"
                      (valueChange)="newAuditNotes.set($event)"
                      placeholder="Enter notes, gauge pressures, or deficiency remarks..."
                      class="h-8 text-xs"
                    />
                  </div>
                </div>
              }
    
              @if (!newAuditSuccess()) {
                <div ui-dialog-footer class="flex items-center justify-end gap-2">
                  <button ui-button variant="outline" size="sm" class="text-xs" (click)="isNewAuditOpen.set(false)">
                    Cancel
                  </button>
                  <button ui-button size="sm" class="gap-1.5 text-xs font-medium" (click)="submitNewAudit()">
                    <lucide-icon [img]="ClipboardCheck" class="size-3.5" />
                    Submit Completed Audit
                  </button>
                </div>
              }
            </ui-dialog-content>
          </ui-dialog>
        </div>
    
        <!-- Vector SVG thumbnail per photo preview type (themed, no baked screenshots). -->
        <ng-template #thumbnail let-type>
          <div class="h-full w-full">
            @if (type === 'gauge') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <circle cx="22" cy="22" r="19" fill="#18181b" stroke="#3f3f46" stroke-width="1.5" />
                <path
                  d="M 12,28 A 14,14 0 1,1 32,28"
                  fill="none"
                  stroke="#22c55e"
                  stroke-width="2"
                  stroke-dasharray="2 2"
                />
                <line x1="22" y1="22" x2="28" y2="15" stroke="#ef4444" stroke-width="1.5" stroke-linecap="round" />
                <circle cx="22" cy="22" r="2.5" fill="#f4f4f5" />
              </svg>
            } @else if (type === 'coil') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <line x1="6" y1="10" x2="38" y2="10" stroke="#38bdf8" stroke-width="1.5" />
                <line x1="6" y1="18" x2="38" y2="18" stroke="#38bdf8" stroke-width="1.5" />
                <line x1="6" y1="26" x2="38" y2="26" stroke="#38bdf8" stroke-width="1.5" />
                <line x1="6" y1="34" x2="38" y2="34" stroke="#38bdf8" stroke-width="1.5" />
                <circle cx="22" cy="22" r="7" fill="#0284c7" fill-opacity="0.3" stroke="#38bdf8" stroke-width="1.5" />
              </svg>
            } @else if (type === 'mount') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="8" y="8" width="28" height="4" rx="1" fill="#71717a" />
                <path d="M 14,12 C 14,18 30,18 30,24 C 30,30 14,30 14,36" fill="none" stroke="#f59e0b" stroke-width="2" />
                <rect x="8" y="36" width="28" height="4" rx="1" fill="#71717a" />
              </svg>
            } @else if (type === 'thermal') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#3b0764" />
                <circle cx="22" cy="22" r="14" fill="#db2777" fill-opacity="0.6" />
                <circle cx="22" cy="22" r="7" fill="#facc15" />
                <line x1="12" y1="22" x2="32" y2="22" stroke="#ffffff" stroke-width="0.75" />
                <line x1="22" y1="12" x2="22" y2="32" stroke="#ffffff" stroke-width="0.75" />
              </svg>
            } @else if (type === 'box') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="6" y="6" width="32" height="32" rx="2" stroke="#eab308" stroke-width="1.5" />
                <line x1="14" y1="12" x2="14" y2="32" stroke="#f97316" stroke-width="2" />
                <line x1="22" y1="12" x2="22" y2="32" stroke="#f97316" stroke-width="2" />
                <circle cx="22" cy="22" r="5" fill="#ef4444" />
              </svg>
            } @else if (type === 'conduit') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="16" y="4" width="12" height="36" fill="#52525b" />
                <circle cx="22" cy="22" r="10" fill="#a1a1aa" fill-opacity="0.3" stroke="#e4e4e7" stroke-width="1.5" />
                <line x1="16" y1="22" x2="28" y2="22" stroke="#ef4444" stroke-width="1.5" />
              </svg>
            } @else if (type === 'riser') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="14" y="4" width="16" height="36" fill="#dc2626" />
                <circle cx="22" cy="18" r="8" fill="#e4e4e7" stroke="#dc2626" stroke-width="1.5" />
                <line x1="22" y1="18" x2="26" y2="14" stroke="#000000" stroke-width="1" />
              </svg>
            } @else if (type === 'pump') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <circle cx="22" cy="22" r="13" fill="#b91c1c" stroke="#f87171" stroke-width="1.5" />
                <circle cx="22" cy="22" r="5" fill="#52525b" />
                <line x1="22" y1="4" x2="22" y2="10" stroke="#f87171" stroke-width="2" />
              </svg>
            } @else if (type === 'switch') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <circle cx="22" cy="16" r="9" stroke="#dc2626" stroke-width="2" fill="none" />
                <rect x="16" y="24" width="12" height="14" rx="2" fill="#eab308" />
              </svg>
            } @else if (type === 'tank') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="10" y="8" width="24" height="28" rx="6" fill="#3f3f46" stroke="#71717a" stroke-width="1.5" />
                <rect x="14" y="14" width="4" height="16" rx="1" fill="#22c55e" />
              </svg>
            } @else if (type === 'battery') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="6" y="12" width="32" height="24" rx="2" fill="#27272a" stroke="#52525b" stroke-width="1.5" />
                <rect x="11" y="8" width="6" height="4" fill="#ef4444" />
                <rect x="27" y="8" width="6" height="4" fill="#3b82f6" />
              </svg>
            } @else if (type === 'ats') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <rect x="6" y="6" width="32" height="32" rx="2" fill="#27272a" stroke="#059669" stroke-width="1.5" />
                <circle cx="14" cy="14" r="2.5" fill="#22c55e" />
                <circle cx="14" cy="22" r="2.5" fill="#eab308" />
                <line x1="22" y1="12" x2="32" y2="12" stroke="#a1a1aa" stroke-width="2" />
              </svg>
            } @else if (type === 'spall') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#27272a" />
                <polygon points="6,6 38,6 38,38 24,38 18,26 6,24" fill="#71717a" />
                <polygon points="18,26 26,24 24,38" fill="#ef4444" fill-opacity="0.4" stroke="#ef4444" stroke-width="1" />
              </svg>
            } @else if (type === 'rebar') {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#27272a" />
                <line x1="6" y1="38" x2="38" y2="6" stroke="#b45309" stroke-width="4" stroke-dasharray="2 1" />
                <circle cx="22" cy="22" r="6" stroke="#ef4444" stroke-width="1.5" fill="none" />
              </svg>
            } @else {
              <svg class="h-full w-full" viewBox="0 0 44 44" fill="none">
                <rect width="44" height="44" fill="#18181b" />
                <path d="M 12,38 L 18,26 L 24,20 L 32,6" stroke="#ef4444" stroke-width="2" fill="none" />
                <rect
                  x="14"
                  y="14"
                  width="16"
                  height="12"
                  rx="1"
                  fill="#ffffff"
                  fill-opacity="0.2"
                  stroke="#ffffff"
                  stroke-width="0.75"
                />
              </svg>
            }
          </div>
        </ng-template>
      `,
    })
    export class UiFieldInspectionManagerComponent {
      protected readonly AlertCircle = AlertCircle
      protected readonly AlertTriangle = AlertTriangle
      protected readonly Building2 = Building2
      protected readonly Calendar = Calendar
      protected readonly Camera = Camera
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly ClipboardCheck = ClipboardCheck
      protected readonly Clock = Clock
      protected readonly Copy = Copy
      protected readonly Download = Download
      protected readonly Eye = Eye
      protected readonly MapPin = MapPin
      protected readonly MoreHorizontal = MoreHorizontal
      protected readonly Percent = Percent
      protected readonly Plus = Plus
      protected readonly Search = Search
      protected readonly ShieldAlert = ShieldAlert
      protected readonly ShieldCheck = ShieldCheck
      protected readonly UploadCloud = UploadCloud
      protected readonly Wrench = Wrench
    
      @Input() set initialFilter(v: InspectionFilter | undefined) {
        this.activeFilter.set(v ?? 'all')
      }
      @Input() set initialRecords(v: InspectionRecord[] | undefined) {
        this.inspectionRecords.set((v ?? defaultRecords).map((r) => ({ ...r })))
      }
      @Input('class') className?: string
    
      readonly activeFilter = signal<InspectionFilter>('all')
      readonly searchQuery = signal('')
      readonly selectedFacility = signal('all')
      readonly copiedGpsId = signal<string | null>(null)
      readonly toastMessage = signal<string | null>(null)
    
      readonly previewPhoto = signal<{ photo: PhotoEvidence; record: InspectionRecord } | null>(null)
      readonly selectedAudit = signal<InspectionRecord | null>(null)
      readonly isNewAuditOpen = signal(false)
    
      readonly inspectionRecords = signal<InspectionRecord[]>(defaultRecords.map((r) => ({ ...r })))
    
      readonly newAuditAsset = signal('AST-HVAC-4091')
      readonly newAuditFacility = signal('Building A · Rooftop Mechanical Room')
      readonly newAuditNotes = signal('')
      readonly newAuditSuccess = signal(false)
    
      private timers: ReturnType<typeof setTimeout>[] = []
    
      constructor() {
        inject(DestroyRef).onDestroy(() => this.timers.forEach((t) => clearTimeout(t)))
      }
    
      readonly filteredRecords = computed(() => {
        const filter = this.activeFilter()
        const facility = this.selectedFacility()
        const q = this.searchQuery().toLowerCase().trim()
        return this.inspectionRecords().filter((record) => {
          if (filter === 'passed' && record.status !== 'passed') return false
          if (filter === 'critical' && record.status !== 'critical') return false
          if (filter === 'minor' && record.status !== 'minor') return false
          if (facility !== 'all' && !record.facility.includes(facility)) return false
          if (q) {
            const hay = `${record.assetName} ${record.assetId} ${record.facility} ${record.category}`.toLowerCase()
            if (!hay.includes(q)) return false
          }
          return true
        })
      })
    
      get rootClass(): string {
        return cn('text-foreground w-full space-y-6', this.className)
      }
    
      filterPillClass(filter: InspectionFilter): string {
        return cn(
          'focus-visible:ring-ring inline-flex cursor-pointer items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
          this.activeFilter() !== filter && 'bg-muted/40 text-muted-foreground hover:bg-muted hover:text-foreground',
          this.activeFilter() === filter &&
            filter === 'all' &&
            'bg-primary text-primary-foreground font-semibold shadow-xs',
          this.activeFilter() === filter && filter === 'passed' && 'bg-success font-semibold text-white shadow-xs',
          this.activeFilter() === filter && filter === 'critical' && 'bg-destructive font-semibold text-white shadow-xs',
          this.activeFilter() === filter && filter === 'minor' && 'bg-warning font-semibold text-white shadow-xs',
        )
      }
    
      scoreClass(status: InspectionStatus): string {
        return cn(
          'font-mono font-semibold',
          status === 'passed' && 'text-success',
          status === 'critical' && 'text-destructive',
          status === 'minor' && 'text-warning',
        )
      }
    
      scoreBarClass(status: InspectionStatus): string {
        return cn(
          'h-full rounded-full transition-[width] duration-300',
          status === 'passed' && 'bg-success',
          status === 'critical' && 'bg-destructive',
          status === 'minor' && 'bg-warning',
        )
      }
    
      statusBadgeClass(status: InspectionStatus): string {
        return cn(
          'text-xs font-semibold capitalize',
          status === 'passed' && 'bg-success hover:bg-success/90 text-white',
          status === 'minor' && 'bg-warning hover:bg-warning/90 text-white',
          status === 'critical' && 'bg-destructive hover:bg-destructive/90 text-white',
        )
      }
    
      signoffClass(status: InspectionStatus): string {
        return cn(
          'inline-flex items-center gap-1 font-mono text-xs font-medium',
          status === 'critical' ? 'text-destructive' : 'text-success',
        )
      }
    
      setSelectedFacility(value: string): void {
        this.selectedFacility.set(value)
      }
    
      copyGps(gps: string, recordId: string): void {
        try {
          void navigator.clipboard?.writeText(gps)
        } catch {
          // Clipboard unavailable (jsdom) — toast still confirms the action.
        }
        this.copiedGpsId.set(recordId)
        this.showToast(`GPS Coordinates ${gps} copied to clipboard`)
        const timer = setTimeout(() => {
          if (this.copiedGpsId() === recordId) this.copiedGpsId.set(null)
        }, 2200)
        this.timers.push(timer)
      }
    
      showToast(msg: string): void {
        this.toastMessage.set(msg)
        const timer = setTimeout(() => {
          if (this.toastMessage() === msg) this.toastMessage.set(null)
        }, 3000)
        this.timers.push(timer)
      }
    
      handleOpenPhoto(photo: PhotoEvidence, record: InspectionRecord): void {
        this.previewPhoto.set({ photo, record })
      }
    
      handleViewReport(record: InspectionRecord): void {
        this.selectedAudit.set(record)
      }
    
      handleDownloadPdf(record: InspectionRecord): void {
        this.showToast(`Downloading certified PDF audit report for ${record.assetId}...`)
      }
    
      handleCreateWorkOrder(record: InspectionRecord): void {
        this.showToast(`Work Order generated for ${record.assetName} (Assigned to Facilities Ops)`)
      }
    
      handleStartNewInspection(): void {
        this.isNewAuditOpen.set(true)
      }
    
      onPreviewOpenChange(open: boolean): void {
        if (!open) this.previewPhoto.set(null)
      }
    
      onAuditOpenChange(open: boolean): void {
        if (!open) this.selectedAudit.set(null)
      }
    
      submitNewAudit(): void {
        this.newAuditSuccess.set(true)
        this.showToast('New audit inspection successfully submitted and recorded!')
        const timer = setTimeout(() => {
          this.isNewAuditOpen.set(false)
          this.newAuditSuccess.set(false)
          this.newAuditNotes.set('')
        }, 1400)
        this.timers.push(timer)
      }
    }
    
  • src/app/components/blocks/field-inspection-manager/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/field-inspection-manager.json