Maintenance log book app — initial scaffold

Multi-department fault log: NewBook-synced room locations + manual
locations with categories, six-state task flow (submitted/in progress/
hold-parts/hold-later/temporary fix/fixed), photos per stage, priorities
with unusable flag and per-task NewBook out-of-order push, costs on
resolve, comment/audit thread, recurring task templates with
note-to-template carryover, asset register, contractor register with
document attachments, staff/contractor allocation, occupancy-aware
summary filter, searchable history with CSV export, email notifications.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jtricerolph 2026-07-03 21:28:57 +00:00
commit 6ca395097e
47 changed files with 6727 additions and 0 deletions

View file

@ -0,0 +1,44 @@
import { AlertTriangle } from 'lucide-react'
import type { Priority, TaskStatus } from '../types'
import { PRIORITY_LABELS, STATUS_LABELS } from '../types'
export function PriorityBadge({ priority }: { priority: Priority }) {
return <span className={`badge badge-prio-${priority}`}>{PRIORITY_LABELS[priority]}</span>
}
export function StatusBadge({ status }: { status: TaskStatus }) {
return <span className={`badge badge-st-${status}`}>{STATUS_LABELS[status]}</span>
}
export function UnusableBadge() {
return (
<span className="badge badge-unusable">
<AlertTriangle size={11} strokeWidth={1.75} />
Unusable
</span>
)
}
export function formatDate(iso: string | null | undefined): string {
if (!iso) return '—'
const d = new Date(iso)
return d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' })
}
export function formatDateTime(iso: string | null | undefined): string {
if (!iso) return '—'
const d = new Date(iso)
return d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' }) + ' ' +
d.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })
}
export function daysOpen(createdAt: string): number {
return Math.floor((Date.now() - new Date(createdAt).getTime()) / 86400000)
}
export function ageLabel(createdAt: string): string {
const days = daysOpen(createdAt)
if (days === 0) return 'today'
if (days === 1) return '1 day'
return `${days} days`
}