From 62d9a2b3045f00061b4bc2ce2ddfd487227ea0df Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 1 Jul 2026 16:48:02 +0000 Subject: [PATCH] Align audit template with SA spec and add premises profile wizard - Rewrite audit seed with official September 2025 section titles (25 sections, 100 items) - Add lib/sections.ts to compute applicableSections from profile flags - Add 5-step premises profile wizard at /premises/[id]/profile - Add profile GET/PUT API with organisation-scoped access - Seed dev organisation and premises for local testing Co-authored-by: jonbur --- app/(app)/layout.tsx | 21 + app/(app)/premises/[id]/profile/page.tsx | 78 ++ app/api/premises/[id]/profile/route.ts | 203 +++ app/layout.tsx | 4 +- app/page.tsx | 27 +- components/profile/ProfileWizard.tsx | 361 +++++ components/ui/form.tsx | 147 ++ lib/auth.ts | 37 + lib/premises.ts | 12 + lib/profile-labels.ts | 79 ++ lib/sections.ts | 118 ++ prisma/data/audit-template-2025-09.ts | 1556 ++++++++++------------ prisma/seed.ts | 53 +- types/audit-template.ts | 3 + types/index.ts | 2 + 15 files changed, 1817 insertions(+), 884 deletions(-) create mode 100644 app/(app)/layout.tsx create mode 100644 app/(app)/premises/[id]/profile/page.tsx create mode 100644 app/api/premises/[id]/profile/route.ts create mode 100644 components/profile/ProfileWizard.tsx create mode 100644 components/ui/form.tsx create mode 100644 lib/auth.ts create mode 100644 lib/premises.ts create mode 100644 lib/profile-labels.ts create mode 100644 lib/sections.ts diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx new file mode 100644 index 0000000..05ff035 --- /dev/null +++ b/app/(app)/layout.tsx @@ -0,0 +1,21 @@ +import Link from "next/link"; + +export default function AppLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( +
+
+
+ + ScoutBase + + Premises compliance +
+
+
{children}
+
+ ); +} diff --git a/app/(app)/premises/[id]/profile/page.tsx b/app/(app)/premises/[id]/profile/page.tsx new file mode 100644 index 0000000..75ef35e --- /dev/null +++ b/app/(app)/premises/[id]/profile/page.tsx @@ -0,0 +1,78 @@ +import { notFound, redirect } from "next/navigation"; +import { requireAuthContext } from "@/lib/auth"; +import { getPremisesForOrganisation } from "@/lib/premises"; +import { EMPTY_PROFILE_FORM } from "@/lib/profile-labels"; +import { ProfileWizard } from "@/components/profile/ProfileWizard"; +import type { ProfileFormData } from "@/lib/profile-labels"; +import type { BuildingAgeBand, FloodRiskZone, OwnershipType } from "@prisma/client"; + +type PageProps = { + params: { id: string }; +}; + +function toFormData(profile: { + ownershipType: OwnershipType; + buildingAgeBand: BuildingAgeBand; + hasGas: boolean; + floodRiskZone: FloodRiskZone | null; + hasCateringKitchen: boolean; + hasGrounds: boolean; + hasSleeping: boolean; + hasVehicles: boolean; + hasPlantMachinery: boolean; + hasThirdPartyUsers: boolean; +}): ProfileFormData { + return { + ownershipType: profile.ownershipType, + buildingAgeBand: profile.buildingAgeBand, + hasGas: profile.hasGas, + floodRiskZone: profile.floodRiskZone, + hasCateringKitchen: profile.hasCateringKitchen, + hasGrounds: profile.hasGrounds, + hasSleeping: profile.hasSleeping, + hasVehicles: profile.hasVehicles, + hasPlantMachinery: profile.hasPlantMachinery, + hasThirdPartyUsers: profile.hasThirdPartyUsers, + }; +} + +export default async function PremisesProfilePage({ params }: PageProps) { + let auth; + try { + auth = await requireAuthContext(); + } catch { + redirect("/"); + } + + const premises = await getPremisesForOrganisation(params.id, auth.organisationId); + + if (!premises) { + notFound(); + } + + const initialProfile = premises.profile + ? toFormData(premises.profile) + : EMPTY_PROFILE_FORM; + + const initialApplicableSections = premises.profile?.applicableSections ?? []; + + return ( +
+

Premises profile

+

{premises.name}

+

{premises.address}

+

+ Answer a few questions about your premises. We'll tailor the annual + safety audit to what applies to your building — usually about five + minutes. +

+
+ +
+
+ ); +} diff --git a/app/api/premises/[id]/profile/route.ts b/app/api/premises/[id]/profile/route.ts new file mode 100644 index 0000000..b3ab61d --- /dev/null +++ b/app/api/premises/[id]/profile/route.ts @@ -0,0 +1,203 @@ +import { NextResponse } from "next/server"; +import type { BuildingAgeBand, FloodRiskZone, OwnershipType } from "@prisma/client"; +import { requireAuthContext } from "@/lib/auth"; +import { getPremisesForOrganisation } from "@/lib/premises"; +import { prisma } from "@/lib/prisma"; +import { computeApplicableSections } from "@/lib/sections"; +import { EMPTY_PROFILE_FORM, type ProfileFormData } from "@/lib/profile-labels"; + +type RouteParams = { params: { id: string } }; + +function toFormData(profile: { + ownershipType: OwnershipType; + buildingAgeBand: BuildingAgeBand; + hasGas: boolean; + floodRiskZone: FloodRiskZone | null; + hasCateringKitchen: boolean; + hasGrounds: boolean; + hasSleeping: boolean; + hasVehicles: boolean; + hasPlantMachinery: boolean; + hasThirdPartyUsers: boolean; +}): ProfileFormData { + return { + ownershipType: profile.ownershipType, + buildingAgeBand: profile.buildingAgeBand, + hasGas: profile.hasGas, + floodRiskZone: profile.floodRiskZone, + hasCateringKitchen: profile.hasCateringKitchen, + hasGrounds: profile.hasGrounds, + hasSleeping: profile.hasSleeping, + hasVehicles: profile.hasVehicles, + hasPlantMachinery: profile.hasPlantMachinery, + hasThirdPartyUsers: profile.hasThirdPartyUsers, + }; +} + +function parseProfileBody(body: unknown): ProfileFormData | null { + if (!body || typeof body !== "object") { + return null; + } + + const data = body as Record; + + return { + ownershipType: (data.ownershipType as OwnershipType) ?? "", + buildingAgeBand: (data.buildingAgeBand as BuildingAgeBand) ?? "", + hasGas: Boolean(data.hasGas), + floodRiskZone: + data.floodRiskZone === null || data.floodRiskZone === "" + ? null + : (data.floodRiskZone as FloodRiskZone), + hasCateringKitchen: Boolean(data.hasCateringKitchen), + hasGrounds: Boolean(data.hasGrounds), + hasSleeping: Boolean(data.hasSleeping), + hasVehicles: Boolean(data.hasVehicles), + hasPlantMachinery: Boolean(data.hasPlantMachinery), + hasThirdPartyUsers: Boolean(data.hasThirdPartyUsers), + }; +} + +function validateProfileForm(form: ProfileFormData): string | null { + if (!form.ownershipType) { + return "Ownership type is required"; + } + if (!form.buildingAgeBand) { + return "Building age is required"; + } + return null; +} + +export async function GET(_request: Request, { params }: RouteParams) { + try { + const auth = await requireAuthContext(); + const premises = await getPremisesForOrganisation(params.id, auth.organisationId); + + if (!premises) { + return NextResponse.json( + { data: null, error: "Premises not found" }, + { status: 404 }, + ); + } + + const form = premises.profile + ? toFormData(premises.profile) + : EMPTY_PROFILE_FORM; + + const applicableSections = premises.profile + ? premises.profile.applicableSections + : []; + + return NextResponse.json({ + data: { + premises: { + id: premises.id, + name: premises.name, + address: premises.address, + }, + profile: form, + applicableSections, + }, + error: null, + }); + } catch (error) { + return NextResponse.json( + { + data: null, + error: error instanceof Error ? error.message : "Failed to load profile", + }, + { status: error instanceof Error && error.message === "Unauthorized" ? 401 : 500 }, + ); + } +} + +export async function PUT(request: Request, { params }: RouteParams) { + try { + const auth = await requireAuthContext(); + const premises = await getPremisesForOrganisation(params.id, auth.organisationId); + + if (!premises) { + return NextResponse.json( + { data: null, error: "Premises not found" }, + { status: 404 }, + ); + } + + const body = await request.json(); + const form = parseProfileBody(body); + + if (!form) { + return NextResponse.json( + { data: null, error: "Invalid request body" }, + { status: 400 }, + ); + } + + const validationError = validateProfileForm(form); + if (validationError) { + return NextResponse.json( + { data: null, error: validationError }, + { status: 400 }, + ); + } + + const applicableSections = computeApplicableSections({ + buildingAgeBand: form.buildingAgeBand as BuildingAgeBand, + hasGas: form.hasGas, + hasSleeping: form.hasSleeping, + hasCateringKitchen: form.hasCateringKitchen, + hasGrounds: form.hasGrounds, + hasVehicles: form.hasVehicles, + hasPlantMachinery: form.hasPlantMachinery, + hasThirdPartyUsers: form.hasThirdPartyUsers, + floodRiskZone: form.floodRiskZone as FloodRiskZone | null, + }); + + const profile = await prisma.premisesProfile.upsert({ + where: { premisesId: premises.id }, + create: { + premisesId: premises.id, + ownershipType: form.ownershipType as OwnershipType, + buildingAgeBand: form.buildingAgeBand as BuildingAgeBand, + hasGas: form.hasGas, + floodRiskZone: form.floodRiskZone as FloodRiskZone | null, + hasCateringKitchen: form.hasCateringKitchen, + hasGrounds: form.hasGrounds, + hasSleeping: form.hasSleeping, + hasVehicles: form.hasVehicles, + hasPlantMachinery: form.hasPlantMachinery, + hasThirdPartyUsers: form.hasThirdPartyUsers, + applicableSections, + }, + update: { + ownershipType: form.ownershipType as OwnershipType, + buildingAgeBand: form.buildingAgeBand as BuildingAgeBand, + hasGas: form.hasGas, + floodRiskZone: form.floodRiskZone as FloodRiskZone | null, + hasCateringKitchen: form.hasCateringKitchen, + hasGrounds: form.hasGrounds, + hasSleeping: form.hasSleeping, + hasVehicles: form.hasVehicles, + hasPlantMachinery: form.hasPlantMachinery, + hasThirdPartyUsers: form.hasThirdPartyUsers, + applicableSections, + }, + }); + + return NextResponse.json({ + data: { + profile: toFormData(profile), + applicableSections: profile.applicableSections, + }, + error: null, + }); + } catch (error) { + return NextResponse.json( + { + data: null, + error: error instanceof Error ? error.message : "Failed to save profile", + }, + { status: error instanceof Error && error.message === "Unauthorized" ? 401 : 500 }, + ); + } +} diff --git a/app/layout.tsx b/app/layout.tsx index 7c92b9e..5842b22 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,8 +2,8 @@ import type { Metadata } from "next"; import "./globals.css"; export const metadata: Metadata = { - title: "Vigil", - description: "Compliance management for volunteer-run community buildings", + title: "ScoutBase", + description: "Premises compliance for volunteer-run community buildings", }; export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index 696ec88..c2dd738 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,10 +1,31 @@ -export default function Home() { +import Link from "next/link"; +import { prisma } from "@/lib/prisma"; + +export default async function Home() { + const premises = await prisma.premises.findFirst({ + orderBy: { createdAt: "asc" }, + select: { id: true, name: true }, + }); + return (
-

Vigil

-

+

ScoutBase

+

Compliance management for volunteer-run community buildings

+ {premises ? ( + + Set up {premises.name} profile + + ) : ( +

+ Run npm run db:seed{" "} + to create a demo premises. +

+ )}
); } diff --git a/components/profile/ProfileWizard.tsx b/components/profile/ProfileWizard.tsx new file mode 100644 index 0000000..02258ac --- /dev/null +++ b/components/profile/ProfileWizard.tsx @@ -0,0 +1,361 @@ +"use client"; + +import { useMemo, useState } from "react"; +import type { + BuildingAgeBand, + FloodRiskZone, + OwnershipType, +} from "@prisma/client"; +import { ChoiceGroup, Fieldset, ToggleField, Button } from "@/components/ui/form"; +import { + BUILDING_AGE_LABELS, + EMPTY_PROFILE_FORM, + FLOOD_RISK_LABELS, + OWNERSHIP_LABELS, + PROFILE_STEPS, + type ProfileFormData, +} from "@/lib/profile-labels"; +import { computeApplicableSections } from "@/lib/sections"; +import { auditTemplate202509 } from "@/prisma/data/audit-template-2025-09"; + +type ProfileWizardProps = { + premisesId: string; + initialProfile: ProfileFormData; + initialApplicableSections: string[]; +}; + +const sectionTitleById = Object.fromEntries( + auditTemplate202509.map((section) => [section.id, section.title]), +); + +function canProceed(stepIndex: number, form: ProfileFormData): boolean { + if (stepIndex === 0) { + return Boolean(form.ownershipType && form.buildingAgeBand); + } + return true; +} + +function previewApplicableSections(form: ProfileFormData): string[] { + if (!form.ownershipType || !form.buildingAgeBand) { + return []; + } + + return computeApplicableSections({ + buildingAgeBand: form.buildingAgeBand, + hasGas: form.hasGas, + hasSleeping: form.hasSleeping, + hasCateringKitchen: form.hasCateringKitchen, + hasGrounds: form.hasGrounds, + hasVehicles: form.hasVehicles, + hasPlantMachinery: form.hasPlantMachinery, + hasThirdPartyUsers: form.hasThirdPartyUsers, + floodRiskZone: + form.floodRiskZone === "" ? null : form.floodRiskZone, + }); +} + +export function ProfileWizard({ + premisesId, + initialProfile, + initialApplicableSections, +}: ProfileWizardProps) { + const [stepIndex, setStepIndex] = useState(0); + const [form, setForm] = useState(initialProfile); + const [savedSections, setSavedSections] = useState(initialApplicableSections); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); + const [saved, setSaved] = useState(false); + + const previewSections = useMemo(() => previewApplicableSections(form), [form]); + const currentStep = PROFILE_STEPS[stepIndex]; + + function updateForm(patch: Partial) { + setForm((current) => ({ ...current, ...patch })); + setSaved(false); + setError(null); + } + + async function saveProfile() { + setIsSaving(true); + setError(null); + + try { + const response = await fetch(`/api/premises/${premisesId}/profile`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(result.error ?? "Failed to save profile"); + } + + setSavedSections(result.data.applicableSections); + setSaved(true); + } catch (saveError) { + setError( + saveError instanceof Error ? saveError.message : "Failed to save profile", + ); + } finally { + setIsSaving(false); + } + } + + async function handleNext() { + if (stepIndex < PROFILE_STEPS.length - 1) { + setStepIndex((index) => index + 1); + return; + } + await saveProfile(); + } + + function handleBack() { + setStepIndex((index) => Math.max(0, index - 1)); + setError(null); + } + + return ( +
+
+
+
+

+ Step {stepIndex + 1} of {PROFILE_STEPS.length} +

+

+ {currentStep.title} +

+

{currentStep.description}

+
+
+
+ {PROFILE_STEPS.map((step, index) => ( +
+ ))} +
+
+ +
+ {stepIndex === 0 ? ( + <> +
+ + name="ownershipType" + value={form.ownershipType} + options={( + Object.entries(OWNERSHIP_LABELS) as [OwnershipType, string][] + ).map(([value, label]) => ({ value, label }))} + onChange={(ownershipType) => updateForm({ ownershipType })} + /> +
+
+ + name="buildingAgeBand" + value={form.buildingAgeBand} + options={( + Object.entries(BUILDING_AGE_LABELS) as [BuildingAgeBand, string][] + ).map(([value, label]) => ({ value, label }))} + onChange={(buildingAgeBand) => updateForm({ buildingAgeBand })} + /> +
+ + ) : null} + + {stepIndex === 1 ? ( + <> +
+ updateForm({ hasGas })} + /> +
+
+ + name="floodRiskZone" + value={form.floodRiskZone ?? ""} + options={[ + { value: "", label: "Not sure / not assessed" }, + ...( + Object.entries(FLOOD_RISK_LABELS) as [FloodRiskZone, string][] + ).map(([value, label]) => ({ value, label })), + ]} + onChange={(floodRiskZone) => + updateForm({ + floodRiskZone: floodRiskZone === "" ? null : floodRiskZone, + }) + } + /> +
+ + ) : null} + + {stepIndex === 2 ? ( +
+ updateForm({ hasCateringKitchen })} + /> + updateForm({ hasGrounds })} + /> + updateForm({ hasSleeping })} + /> +
+ ) : null} + + {stepIndex === 3 ? ( +
+ updateForm({ hasVehicles })} + /> + updateForm({ hasPlantMachinery })} + /> + updateForm({ hasThirdPartyUsers })} + /> +
+ ) : null} + + {stepIndex === 4 ? ( +
+
+

Your answers

+
    +
  • + Ownership:{" "} + {form.ownershipType + ? OWNERSHIP_LABELS[form.ownershipType] + : "—"} +
  • +
  • + Building age:{" "} + {form.buildingAgeBand + ? BUILDING_AGE_LABELS[form.buildingAgeBand] + : "—"} +
  • +
  • Gas on site: {form.hasGas ? "Yes" : "No"}
  • +
  • + Flood risk:{" "} + {form.floodRiskZone ? FLOOD_RISK_LABELS[form.floodRiskZone] : "Not set"} +
  • +
  • Kitchen: {form.hasCateringKitchen ? "Yes" : "No"}
  • +
  • Grounds: {form.hasGrounds ? "Yes" : "No"}
  • +
  • Sleeping: {form.hasSleeping ? "Yes" : "No"}
  • +
  • Vehicles: {form.hasVehicles ? "Yes" : "No"}
  • +
  • Plant/machinery: {form.hasPlantMachinery ? "Yes" : "No"}
  • +
  • Third-party hire: {form.hasThirdPartyUsers ? "Yes" : "No"}
  • +
+
+ +
+

+ {previewSections.length} audit sections will apply +

+

+ Based on your profile, we'll include the relevant sections + from the Scout Association September 2025 audit template. +

+
    + {previewSections.map((sectionId) => ( +
  • + {sectionTitleById[sectionId] ?? sectionId} +
  • + ))} +
+
+ + {saved ? ( +
+ Profile saved. {savedSections.length} audit sections will apply to + your next audit. +
+ ) : null} +
+ ) : null} + + {error ? ( +
+ {error} +
+ ) : null} +
+ +
+ +
+ {stepIndex === PROFILE_STEPS.length - 1 && saved ? ( + + ) : null} + +
+
+
+ ); +} diff --git a/components/ui/form.tsx b/components/ui/form.tsx new file mode 100644 index 0000000..fd69cae --- /dev/null +++ b/components/ui/form.tsx @@ -0,0 +1,147 @@ +"use client"; + +import type { ReactNode } from "react"; + +type FieldsetProps = { + legend: string; + description?: string; + children: ReactNode; +}; + +export function Fieldset({ legend, description, children }: FieldsetProps) { + return ( +
+ {legend} + {description ? ( +

{description}

+ ) : null} +
{children}
+
+ ); +} + +type ChoiceOption = { + value: T; + label: string; + description?: string; +}; + +type ChoiceGroupProps = { + name: string; + value: T | ""; + options: ChoiceOption[]; + onChange: (value: T) => void; +}; + +export function ChoiceGroup({ + name, + value, + options, + onChange, +}: ChoiceGroupProps) { + return ( +
+ {options.map((option) => { + const checked = value === option.value; + return ( + + ); + })} +
+ ); +} + +type ToggleFieldProps = { + label: string; + description?: string; + checked: boolean; + onChange: (checked: boolean) => void; +}; + +export function ToggleField({ + label, + description, + checked, + onChange, +}: ToggleFieldProps) { + return ( + + ); +} + +type ButtonProps = { + children: ReactNode; + onClick?: () => void; + type?: "button" | "submit"; + variant?: "primary" | "secondary"; + disabled?: boolean; +}; + +export function Button({ + children, + onClick, + type = "button", + variant = "primary", + disabled = false, +}: ButtonProps) { + const base = + "inline-flex items-center justify-center rounded-lg px-4 py-2.5 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"; + const styles = + variant === "primary" + ? "bg-emerald-700 text-white hover:bg-emerald-800" + : "border border-slate-300 bg-white text-slate-700 hover:bg-slate-50"; + + return ( + + ); +} diff --git a/lib/auth.ts b/lib/auth.ts new file mode 100644 index 0000000..795a44d --- /dev/null +++ b/lib/auth.ts @@ -0,0 +1,37 @@ +import { prisma } from "@/lib/prisma"; + +export type AuthContext = { + userId: string; + organisationId: string; +}; + +/** Development user ID until Supabase Auth is integrated. */ +export const DEV_USER_ID = "dev-user-scoutbase"; + +/** + * Returns the authenticated user's context. + * TODO: Replace with Supabase Auth session. + */ +export async function getAuthContext(): Promise { + const membership = await prisma.membership.findFirst({ + orderBy: { createdAt: "asc" }, + select: { userId: true, organisationId: true }, + }); + + if (!membership) { + return null; + } + + return { + userId: membership.userId, + organisationId: membership.organisationId, + }; +} + +export async function requireAuthContext(): Promise { + const context = await getAuthContext(); + if (!context) { + throw new Error("Unauthorized"); + } + return context; +} diff --git a/lib/premises.ts b/lib/premises.ts new file mode 100644 index 0000000..03e1830 --- /dev/null +++ b/lib/premises.ts @@ -0,0 +1,12 @@ +import type { Premises, PremisesProfile } from "@prisma/client"; +import { prisma } from "@/lib/prisma"; + +export async function getPremisesForOrganisation( + premisesId: string, + organisationId: string, +): Promise<(Premises & { profile: PremisesProfile | null }) | null> { + return prisma.premises.findFirst({ + where: { id: premisesId, organisationId }, + include: { profile: true }, + }); +} diff --git a/lib/profile-labels.ts b/lib/profile-labels.ts new file mode 100644 index 0000000..56a3576 --- /dev/null +++ b/lib/profile-labels.ts @@ -0,0 +1,79 @@ +import type { + BuildingAgeBand, + FloodRiskZone, + OwnershipType, +} from "@prisma/client"; + +export const OWNERSHIP_LABELS: Record = { + OWNED: "We own the building", + LEASED: "We hold a long lease", + HIRED: "We hire or share the premises", +}; + +export const BUILDING_AGE_LABELS: Record = { + PRE_1985: "Built before 1985", + Y1985_2000: "Built between 1985 and 2000", + POST_2000: "Built after 2000", +}; + +export const FLOOD_RISK_LABELS: Record = { + LOW: "Low flood risk", + MEDIUM: "Medium flood risk", + HIGH: "High flood risk", +}; + +export const PROFILE_STEPS = [ + { + id: "building", + title: "Your building", + description: "Tell us about ownership and when the building was constructed.", + }, + { + id: "utilities", + title: "Utilities & environment", + description: "Gas supply and local flood risk affect which checks apply.", + }, + { + id: "facilities", + title: "Facilities on site", + description: "Kitchens, grounds, and sleeping accommodation change your audit.", + }, + { + id: "operations", + title: "How the premises is used", + description: "Vehicles, machinery, and third-party hire.", + }, + { + id: "review", + title: "Review & save", + description: "Check your answers and see which audit sections will apply.", + }, +] as const; + +export type ProfileStepId = (typeof PROFILE_STEPS)[number]["id"]; + +export type ProfileFormData = { + ownershipType: OwnershipType | ""; + buildingAgeBand: BuildingAgeBand | ""; + hasGas: boolean; + floodRiskZone: FloodRiskZone | "" | null; + hasCateringKitchen: boolean; + hasGrounds: boolean; + hasSleeping: boolean; + hasVehicles: boolean; + hasPlantMachinery: boolean; + hasThirdPartyUsers: boolean; +}; + +export const EMPTY_PROFILE_FORM: ProfileFormData = { + ownershipType: "", + buildingAgeBand: "", + hasGas: false, + floodRiskZone: null, + hasCateringKitchen: false, + hasGrounds: false, + hasSleeping: false, + hasVehicles: false, + hasPlantMachinery: false, + hasThirdPartyUsers: false, +}; diff --git a/lib/sections.ts b/lib/sections.ts new file mode 100644 index 0000000..142b756 --- /dev/null +++ b/lib/sections.ts @@ -0,0 +1,118 @@ +import type { BuildingAgeBand, FloodRiskZone } from "@prisma/client"; + +/** Section IDs aligned with SA Safe Scouting Premises Audit Tool (September 2025). */ +export const AUDIT_SECTION_IDS = [ + "organisation-safety", + "monitoring-incidents", + "fire", + "emergency-procedures", + "electrical", + "gas", + "asbestos", + "water-legionella", + "third-parties", + "access", + "coshh", + "equipment", + "flood-risk", + "staff-volunteers", + "guests-visitors", + "first-aid", + "contractor-management", + "safeguarding", + "manual-handling", + "catering", + "sleeping-accommodation", + "plant-machinery", + "vehicles", + "ppe", + "trees-grounds", +] as const; + +export type AuditSectionId = (typeof AUDIT_SECTION_IDS)[number]; + +export type ProfileForSections = { + buildingAgeBand: BuildingAgeBand; + hasGas: boolean; + hasSleeping: boolean; + hasCateringKitchen: boolean; + hasGrounds: boolean; + hasVehicles: boolean; + hasPlantMachinery: boolean; + hasThirdPartyUsers: boolean; + floodRiskZone: FloodRiskZone | null; +}; + +export function isLargerPremises(profile: ProfileForSections): boolean { + return ( + profile.hasSleeping || + profile.hasCateringKitchen || + profile.hasGrounds || + profile.hasVehicles || + profile.hasPlantMachinery || + profile.hasThirdPartyUsers + ); +} + +function isSectionApplicable( + sectionId: AuditSectionId, + profile: ProfileForSections, +): boolean { + switch (sectionId) { + case "organisation-safety": + case "monitoring-incidents": + case "fire": + case "emergency-procedures": + case "electrical": + case "water-legionella": + case "third-parties": + case "access": + case "coshh": + case "equipment": + case "flood-risk": + case "safeguarding": + return true; + case "gas": + return profile.hasGas; + case "asbestos": + return profile.buildingAgeBand !== "POST_2000"; + case "staff-volunteers": + case "first-aid": + case "manual-handling": + return isLargerPremises(profile); + case "guests-visitors": + return profile.hasThirdPartyUsers || profile.hasSleeping; + case "contractor-management": + return profile.hasThirdPartyUsers || isLargerPremises(profile); + case "catering": + return profile.hasCateringKitchen; + case "sleeping-accommodation": + return profile.hasSleeping; + case "plant-machinery": + return profile.hasPlantMachinery; + case "vehicles": + return profile.hasVehicles; + case "ppe": + return ( + isLargerPremises(profile) && + (profile.hasPlantMachinery || profile.hasCateringKitchen) + ); + case "trees-grounds": + return profile.hasGrounds; + default: + return true; + } +} + +export function computeApplicableSections( + profile: ProfileForSections, + sectionIds: readonly string[] = AUDIT_SECTION_IDS, +): string[] { + return sectionIds.filter((id) => + isSectionApplicable(id as AuditSectionId, profile), + ); +} + +export function countApplicableSections(profile: ProfileForSections): number { + return computeApplicableSections(profile).length; +} diff --git a/prisma/data/audit-template-2025-09.ts b/prisma/data/audit-template-2025-09.ts index d07a645..e573b91 100644 --- a/prisma/data/audit-template-2025-09.ts +++ b/prisma/data/audit-template-2025-09.ts @@ -1,883 +1,683 @@ -import type { AuditTemplateSections } from "@/types/audit-template"; +import type { + AuditTemplateItem, + AuditTemplateSection, + AuditTemplateSections, + AuditSectionScope, +} from "@/types/audit-template"; + +function item( + id: string, + question: string, + guidance: string, + tags: string[] = [], + responseType: AuditTemplateItem["responseType"] = "yes_no_na_action", +): AuditTemplateItem { + return { id, question, guidance, responseType, tags }; +} + +function section( + number: number, + id: string, + title: string, + scope: AuditSectionScope, + items: AuditTemplateItem[], +): AuditTemplateSection { + return { number, id, title, scope, items }; +} /** - * Annual safety audit template v2025-09. - * 25 sections, ~100 check items for UK Scout hut premises. + * Safe Scouting Premises Audit Tool — September 2025. + * Section titles from Scout Association Appendix A; 4 items per section (100 total). */ export const auditTemplate202509: AuditTemplateSections = [ - { - id: "governance", - number: 1, - title: "Governance and documentation", - items: [ - { - id: "gov-1", - question: "Is there a current premises management plan?", - guidance: "Check for a written plan covering maintenance, inspections, and responsibilities.", - responseType: "yes_no_na_action", - tags: ["governance"], - }, - { - id: "gov-2", - question: "Are trustee meeting minutes available for the last 12 months?", - guidance: "Minutes should record premises-related decisions and actions.", - responseType: "yes_no_na_action", - tags: ["governance"], - }, - { - id: "gov-3", - question: "Is premises insurance current and adequate?", - guidance: "Verify policy covers buildings, contents, and public liability.", - responseType: "yes_no_na_action", - tags: ["governance", "insurance"], - }, - { - id: "gov-4", - question: "Is there a designated premises manager with documented responsibilities?", - guidance: "Role should be assigned and communicated to the trustee board.", - responseType: "yes_no_na_action", - tags: ["governance"], - }, - ], - }, - { - id: "fire-general", - number: 2, - title: "Fire safety — general", - items: [ - { - id: "fire-1", - question: "Is a current fire risk assessment available?", - guidance: "FRA should be reviewed annually or after significant changes.", - responseType: "yes_no_na_action", - tags: ["fire"], - }, - { - id: "fire-2", - question: "Are fire escape routes clearly marked and unobstructed?", - guidance: "Check all exit routes during a walk-through.", - responseType: "yes_no_na_action", - tags: ["fire"], - }, - { - id: "fire-3", - question: "Are fire doors self-closing and in good condition?", - guidance: "Do not wedge fire doors open. Check seals and closers.", - responseType: "yes_no_na_action", - tags: ["fire"], - }, - { - id: "fire-4", - question: "Is the fire assembly point clearly signed and accessible?", - guidance: "Assembly point should be away from the building and known to leaders.", - responseType: "yes_no_na_action", - tags: ["fire"], - }, - ], - }, - { - id: "fire-sleeping", - number: 3, - title: "Fire safety — sleeping accommodation", - items: [ - { - id: "fire-slp-1", - question: "Are sleeping areas excluded from the audit premises profile if not applicable?", - guidance: "This section applies only when hasSleeping is true on the premises profile.", - responseType: "yes_no_na", - tags: ["fire", "sleeping", "profile:hasSleeping"], - }, - { - id: "fire-slp-2", - question: "Are smoke detectors installed in all sleeping areas?", - guidance: "Mains-wired with battery backup is preferred for sleeping accommodation.", - responseType: "yes_no_na_action", - tags: ["fire", "sleeping", "profile:hasSleeping"], - }, - { - id: "fire-slp-3", - question: "Is a night-time evacuation plan documented and practised?", - guidance: "Include roles for waking young people and accounting for all occupants.", - responseType: "yes_no_na_action", - tags: ["fire", "sleeping", "profile:hasSleeping"], - }, - { - id: "fire-slp-4", - question: "Are portable heaters prohibited in sleeping areas?", - guidance: "Fixed heating only in sleeping accommodation.", - responseType: "yes_no_na_action", - tags: ["fire", "sleeping", "profile:hasSleeping"], - }, - ], - }, - { - id: "electrical", - number: 4, - title: "Electrical safety", - items: [ - { - id: "elec-1", - question: "Is there a current EICR (Electrical Installation Condition Report)?", - guidance: "EICR should be within its validity period (typically 5 years).", - responseType: "yes_no_na_action", - tags: ["electrical", "eicr"], - }, - { - id: "elec-2", - question: "Is PAT testing up to date for portable appliances?", - guidance: "Check PAT labels and test records for all portable equipment.", - responseType: "yes_no_na_action", - tags: ["electrical", "pat"], - }, - { - id: "elec-3", - question: "Are consumer units and distribution boards accessible and labelled?", - guidance: "No obstructions; circuits should be labelled.", - responseType: "yes_no_na_action", - tags: ["electrical"], - }, - { - id: "elec-4", - question: "Are extension leads and multi-way adapters used safely?", - guidance: "No daisy-chaining; avoid overload.", - responseType: "yes_no_na_action", - tags: ["electrical"], - }, - ], - }, - { - id: "gas", - number: 5, - title: "Gas safety", - items: [ - { - id: "gas-1", - question: "Does this premises have gas installations?", - guidance: "Skip if hasGas is false on the premises profile.", - responseType: "yes_no_na", - tags: ["gas", "profile:hasGas"], - }, - { - id: "gas-2", - question: "Is there a current Gas Safe certificate?", - guidance: "Annual inspection by a Gas Safe registered engineer.", - responseType: "yes_no_na_action", - tags: ["gas", "profile:hasGas"], - }, - { - id: "gas-3", - question: "Are gas appliances serviced annually?", - guidance: "Check service records for boilers, heaters, and cookers.", - responseType: "yes_no_na_action", - tags: ["gas", "profile:hasGas"], - }, - { - id: "gas-4", - question: "Is the gas emergency shut-off valve accessible and labelled?", - guidance: "All users should know the location of the emergency control.", - responseType: "yes_no_na_action", - tags: ["gas", "profile:hasGas"], - }, - ], - }, - { - id: "water-legionella", - number: 6, - title: "Water and legionella", - items: [ - { - id: "leg-1", - question: "Is there a legionella risk assessment for water systems?", - guidance: "Required where stored water or infrequent use creates risk.", - responseType: "yes_no_na_action", - tags: ["legionella", "water"], - }, - { - id: "leg-2", - question: "Are little-used outlets flushed regularly?", - guidance: "Run taps/showers weekly in little-used areas.", - responseType: "yes_no_na_action", - tags: ["legionella", "water"], - }, - { - id: "leg-3", - question: "Are water temperatures at outlets appropriate?", - guidance: "Hot water should reach 50°C+ at outlets; cold below 20°C.", - responseType: "yes_no_na_action", - tags: ["legionella", "water"], - }, - { - id: "leg-4", - question: "Is dead leg pipework identified and managed?", - guidance: "Remove or flush redundant pipework.", - responseType: "yes_no_na_action", - tags: ["legionella", "water"], - }, - ], - }, - { - id: "asbestos", - number: 7, - title: "Asbestos management", - items: [ - { - id: "asb-1", - question: "Has an asbestos survey been carried out for pre-2000 buildings?", - guidance: "Required for buildingAgeBand PRE_1985 or Y1985_2000.", - responseType: "yes_no_na_action", - tags: ["asbestos", "profile:buildingAgeBand"], - }, - { - id: "asb-2", - question: "Is there an asbestos management plan if ACMs are present?", - guidance: "Plan should include monitoring and control measures.", - responseType: "yes_no_na_action", - tags: ["asbestos"], - }, - { - id: "asb-3", - question: "Are contractors informed of asbestos status before works?", - guidance: "Provide asbestos register to all contractors.", - responseType: "yes_no_na_action", - tags: ["asbestos"], - }, - { - id: "asb-4", - question: "Are damaged asbestos-containing materials reported and managed?", - guidance: "Do not disturb; seek specialist advice immediately.", - responseType: "yes_no_na_action", - tags: ["asbestos"], - }, - ], - }, - { - id: "coshh", - number: 8, - title: "COSHH and hazardous substances", - items: [ - { - id: "coshh-1", - question: "Are cleaning chemicals stored securely with COSHH assessments?", - guidance: "Safety data sheets should be available on site.", - responseType: "yes_no_na_action", - tags: ["coshh"], - }, - { - id: "coshh-2", - question: "Are hazardous substances labelled and in original containers?", - guidance: "No decanting into unlabelled containers.", - responseType: "yes_no_na_action", - tags: ["coshh"], - }, - { - id: "coshh-3", - question: "Is PPE available where required by COSHH assessments?", - guidance: "Gloves, goggles, etc. as specified on SDS.", - responseType: "yes_no_na_action", - tags: ["coshh"], - }, - { - id: "coshh-4", - question: "Are spill kits available for chemical storage areas?", - guidance: "Particularly relevant for kitchen and cleaning stores.", - responseType: "yes_no_na_action", - tags: ["coshh"], - }, - ], - }, - { - id: "first-aid", - number: 9, - title: "First aid", - items: [ - { - id: "fa-1", - question: "Are first aid kits stocked and accessible?", - guidance: "Check contents against a checklist; replace used items.", - responseType: "yes_no_na_action", - tags: ["first_aid"], - }, - { - id: "fa-2", - question: "Is there a documented first aid needs assessment?", - guidance: "Consider number of users, activities, and remoteness.", - responseType: "yes_no_na_action", - tags: ["first_aid"], - }, - { - id: "fa-3", - question: "Are sufficient trained first aiders available during activities?", - guidance: "At least one first aider should be present during section meetings.", - responseType: "yes_no_na_action", - tags: ["first_aid"], - }, - { - id: "fa-4", - question: "Is an accident book maintained and reviewed?", - guidance: "Record all incidents; review trends at trustee meetings.", - responseType: "yes_no_na_action", - tags: ["first_aid"], - }, - ], - }, - { - id: "safeguarding", - number: 10, - title: "Safeguarding", - items: [ - { - id: "sg-1", - question: "Are safeguarding policies displayed and accessible?", - guidance: "Yellow Card and safeguarding contacts should be visible.", - responseType: "yes_no_na_action", - tags: ["safeguarding"], - }, - { - id: "sg-2", - question: "Are changing and toilet facilities appropriate and supervised?", - guidance: "Consider ratios and layout for young people.", - responseType: "yes_no_na_action", - tags: ["safeguarding"], - }, - { - id: "sg-3", - question: "Is the premises layout conducive to good safeguarding practice?", - guidance: "Avoid isolated areas without oversight.", - responseType: "yes_no_na_action", - tags: ["safeguarding"], - }, - { - id: "sg-4", - question: "Are external groups using the premises vetted appropriately?", - guidance: "Applies when hasThirdPartyUsers is true.", - responseType: "yes_no_na_action", - tags: ["safeguarding", "profile:hasThirdPartyUsers"], - }, - ], - }, - { - id: "security", - number: 11, - title: "Security", - items: [ - { - id: "sec-1", - question: "Are external doors and windows secure when the premises is unoccupied?", - guidance: "Check locks, shutters, and access control.", - responseType: "yes_no_na_action", - tags: ["security"], - }, - { - id: "sec-2", - question: "Is there a key/asset management system?", - guidance: "Keys should be issued to authorised persons only.", - responseType: "yes_no_na_action", - tags: ["security"], - }, - { - id: "sec-3", - question: "Is external lighting adequate for evening use?", - guidance: "Car park and entrance routes should be lit.", - responseType: "yes_no_na_action", - tags: ["security"], - }, - { - id: "sec-4", - question: "Is there an intruder alarm or CCTV where justified by risk?", - guidance: "Document the rationale if not installed.", - responseType: "yes_no_na_action", - tags: ["security"], - }, - ], - }, - { - id: "access-egress", - number: 12, - title: "Access and egress", - items: [ - { - id: "acc-1", - question: "Are access routes free from trip hazards?", - guidance: "Check paths, steps, and ramps.", - responseType: "yes_no_na_action", - tags: ["access"], - }, - { - id: "acc-2", - question: "Is disabled access provided where reasonably practicable?", - guidance: "Consider ramps, door widths, and toilet facilities.", - responseType: "yes_no_na_action", - tags: ["access"], - }, - { - id: "acc-3", - question: "Are handrails provided and secure on stairs and ramps?", - guidance: "Check fixings and condition.", - responseType: "yes_no_na_action", - tags: ["access"], - }, - { - id: "acc-4", - question: "Is emergency egress available from all occupied areas?", - guidance: "No dead ends without alternative escape.", - responseType: "yes_no_na_action", - tags: ["access", "fire"], - }, - ], - }, - { - id: "structural", - number: 13, - title: "Structural and building fabric", - items: [ - { - id: "str-1", - question: "Are roofs, gutters, and downpipes in good repair?", - guidance: "Look for leaks, missing tiles, and blocked gutters.", - responseType: "yes_no_na_action", - tags: ["structural"], - }, - { - id: "str-2", - question: "Are internal and external walls free from significant defects?", - guidance: "Cracks, damp, and bowing should be investigated.", - responseType: "yes_no_na_action", - tags: ["structural"], - }, - { - id: "str-3", - question: "Are floors level and free from significant damage?", - guidance: "Trip hazards and soft spots should be addressed.", - responseType: "yes_no_na_action", - tags: ["structural"], - }, - { - id: "str-4", - question: "Is a planned maintenance schedule in place?", - guidance: "Schedule should cover fabric, services, and grounds.", - responseType: "yes_no_na_action", - tags: ["structural", "governance"], - }, - ], - }, - { - id: "kitchen", - number: 14, - title: "Kitchen and catering", - items: [ - { - id: "kit-1", - question: "Does this premises have a catering kitchen?", - guidance: "Skip if hasCateringKitchen is false on the premises profile.", - responseType: "yes_no_na", - tags: ["kitchen", "profile:hasCateringKitchen"], - }, - { - id: "kit-2", - question: "Is food hygiene training current for food handlers?", - guidance: "Level 2 Food Safety certificate recommended.", - responseType: "yes_no_na_action", - tags: ["kitchen", "profile:hasCateringKitchen"], - }, - { - id: "kit-3", - question: "Are kitchen surfaces, equipment, and storage hygienic?", - guidance: "Check cleanliness, fridge temperatures, and pest control.", - responseType: "yes_no_na_action", - tags: ["kitchen", "profile:hasCateringKitchen"], - }, - { - id: "kit-4", - question: "Is extraction and ventilation adequate in the kitchen?", - guidance: "Canopy and filters should be cleaned regularly.", - responseType: "yes_no_na_action", - tags: ["kitchen", "profile:hasCateringKitchen"], - }, - ], - }, - { - id: "grounds", - number: 15, - title: "Grounds and external areas", - items: [ - { - id: "grd-1", - question: "Does this premises include external grounds?", - guidance: "Skip if hasGrounds is false on the premises profile.", - responseType: "yes_no_na", - tags: ["grounds", "profile:hasGrounds"], - }, - { - id: "grd-2", - question: "Are paths, car parks, and play areas maintained safely?", - guidance: "Check for potholes, uneven surfaces, and debris.", - responseType: "yes_no_na_action", - tags: ["grounds", "profile:hasGrounds"], - }, - { - id: "grd-3", - question: "Are trees and vegetation managed to reduce risk?", - guidance: "Overhanging branches and deadwood should be addressed.", - responseType: "yes_no_na_action", - tags: ["grounds", "profile:hasGrounds"], - }, - { - id: "grd-4", - question: "Is external waste storage secure and compliant?", - guidance: "Bins should not obstruct fire routes.", - responseType: "yes_no_na_action", - tags: ["grounds", "profile:hasGrounds"], - }, - ], - }, - { - id: "vehicles", - number: 16, - title: "Vehicles and transport", - items: [ - { - id: "veh-1", - question: "Are vehicles stored or maintained on site?", - guidance: "Skip if hasVehicles is false on the premises profile.", - responseType: "yes_no_na", - tags: ["vehicles", "profile:hasVehicles"], - }, - { - id: "veh-2", - question: "Is vehicle storage area segregated from pedestrian routes?", - guidance: "Minimise interaction between vehicles and young people.", - responseType: "yes_no_na_action", - tags: ["vehicles", "profile:hasVehicles"], - }, - { - id: "veh-3", - question: "Are fuel and oil stored safely with spill containment?", - guidance: "Use appropriate containers and bunding.", - responseType: "yes_no_na_action", - tags: ["vehicles", "profile:hasVehicles"], - }, - { - id: "veh-4", - question: "Are minibus and trailer checks documented if applicable?", - guidance: "Daily walk-around checks and MOT/service records.", - responseType: "yes_no_na_action", - tags: ["vehicles", "profile:hasVehicles"], - }, - ], - }, - { - id: "plant-machinery", - number: 17, - title: "Plant and machinery", - items: [ - { - id: "plt-1", - question: "Is plant or machinery used on site?", - guidance: "Skip if hasPlantMachinery is false on the premises profile.", - responseType: "yes_no_na", - tags: ["plant", "profile:hasPlantMachinery"], - }, - { - id: "plt-2", - question: "Are risk assessments in place for plant and machinery?", - guidance: "Include training requirements and safe systems of work.", - responseType: "yes_no_na_action", - tags: ["plant", "profile:hasPlantMachinery"], - }, - { - id: "plt-3", - question: "Are guards and safety devices in place and functional?", - guidance: "Do not operate machinery with guards removed.", - responseType: "yes_no_na_action", - tags: ["plant", "profile:hasPlantMachinery"], - }, - { - id: "plt-4", - question: "Is maintenance and inspection documented?", - guidance: "LOLER checks where applicable for lifting equipment.", - responseType: "yes_no_na_action", - tags: ["plant", "profile:hasPlantMachinery"], - }, - ], - }, - { - id: "flood-risk", - number: 18, - title: "Flood risk", - items: [ - { - id: "fld-1", - question: "Has a flood risk assessment been completed?", - guidance: "Required for MEDIUM or HIGH floodRiskZone on the profile.", - responseType: "yes_no_na_action", - tags: ["flood", "profile:floodRiskZone"], - }, - { - id: "fld-2", - question: "Are flood resilience measures in place where required?", - guidance: "Flood gates, raised electrics, sandbag stores as appropriate.", - responseType: "yes_no_na_action", - tags: ["flood", "profile:floodRiskZone"], - }, - { - id: "fld-3", - question: "Is there a flood emergency plan?", - guidance: "Include evacuation triggers and contact numbers.", - responseType: "yes_no_na_action", - tags: ["flood", "profile:floodRiskZone"], - }, - { - id: "fld-4", - question: "Are drains and gullies maintained to reduce surface water risk?", - guidance: "Clear blockages before winter months.", - responseType: "yes_no_na_action", - tags: ["flood"], - }, - ], - }, - { - id: "emergency-lighting", - number: 19, - title: "Emergency lighting", - items: [ - { - id: "eml-1", - question: "Is emergency lighting installed where required?", - guidance: "Required on escape routes and open areas.", - responseType: "yes_no_na_action", - tags: ["emergency_lighting", "fire"], - }, - { - id: "eml-2", - question: "Has annual emergency lighting testing been completed?", - guidance: "Monthly flick tests and annual duration test.", - responseType: "yes_no_na_action", - tags: ["emergency_lighting"], - }, - { - id: "eml-3", - question: "Are emergency lighting test records available?", - guidance: "Log book should show monthly and annual tests.", - responseType: "yes_no_na_action", - tags: ["emergency_lighting"], - }, - { - id: "eml-4", - question: "Are all emergency luminaires unobstructed?", - guidance: "No stored items blocking light output.", - responseType: "yes_no_na_action", - tags: ["emergency_lighting"], - }, - ], - }, - { - id: "fire-extinguishers", - number: 20, - title: "Fire extinguishers", - items: [ - { - id: "ext-1", - question: "Are fire extinguishers provided at appropriate locations?", - guidance: "Type and siting should match the fire risk assessment.", - responseType: "yes_no_na_action", - tags: ["fire_extinguisher", "fire"], - }, - { - id: "ext-2", - question: "Have extinguishers been serviced within the last 12 months?", - guidance: "Annual service by a competent contractor.", - responseType: "yes_no_na_action", - tags: ["fire_extinguisher"], - }, - { - id: "ext-3", - question: "Are extinguishers mounted correctly and signed?", - guidance: "Red signage at each extinguisher location.", - responseType: "yes_no_na_action", - tags: ["fire_extinguisher"], - }, - { - id: "ext-4", - question: "Are staff/volunteers aware of extinguisher locations?", - guidance: "Brief new leaders as part of premises induction.", - responseType: "yes_no_na_action", - tags: ["fire_extinguisher"], - }, - ], - }, - { - id: "contractors", - number: 21, - title: "Contractors and third-party users", - items: [ - { - id: "con-1", - question: "Is contractor insurance verified before work commences?", - guidance: "Public liability minimum £5m recommended.", - responseType: "yes_no_na_action", - tags: ["contractors", "profile:hasThirdPartyUsers"], - }, - { - id: "con-2", - question: "Are hot work permits used where applicable?", - guidance: "Required for welding, cutting, and other ignition sources.", - responseType: "yes_no_na_action", - tags: ["contractors"], - }, - { - id: "con-3", - question: "Are hire agreements in place for third-party users?", - guidance: "Applies when hasThirdPartyUsers is true.", - responseType: "yes_no_na_action", - tags: ["contractors", "profile:hasThirdPartyUsers"], - }, - { - id: "con-4", - question: "Are lone working procedures in place for contractors?", - guidance: "Check-in arrangements for out-of-hours work.", - responseType: "yes_no_na_action", - tags: ["contractors"], - }, - ], - }, - { - id: "lone-working", - number: 22, - title: "Lone working and out-of-hours use", - items: [ - { - id: "lw-1", - question: "Is there a lone working policy for premises volunteers?", - guidance: "Define when lone working is permitted and controls required.", - responseType: "yes_no_na_action", - tags: ["lone_working"], - }, - { - id: "lw-2", - question: "Are out-of-hours access arrangements documented?", - guidance: "Key holders and alarm codes should be controlled.", - responseType: "yes_no_na_action", - tags: ["lone_working"], - }, - { - id: "lw-3", - question: "Is adequate lighting available for lone workers?", - guidance: "Internal and external lighting on timers or sensors.", - responseType: "yes_no_na_action", - tags: ["lone_working"], - }, - { - id: "lw-4", - question: "Is a check-in procedure in place for lone workers?", - guidance: "Buddy system or scheduled contact checks.", - responseType: "yes_no_na_action", - tags: ["lone_working"], - }, - ], - }, - { - id: "welfare", - number: 23, - title: "Welfare facilities", - items: [ - { - id: "wel-1", - question: "Are toilet and wash facilities adequate and clean?", - guidance: "Sufficient for peak occupancy; hot water available.", - responseType: "yes_no_na_action", - tags: ["welfare"], - }, - { - id: "wel-2", - question: "Is drinking water available?", - guidance: "Mains supply or bottled water for events.", - responseType: "yes_no_na_action", - tags: ["welfare"], - }, - { - id: "wel-3", - question: "Are heating and ventilation systems maintained?", - guidance: "Annual service for boilers; filters cleaned.", - responseType: "yes_no_na_action", - tags: ["welfare"], - }, - { - id: "wel-4", - question: "Is the premises free from excessive cold, heat, or draughts?", - guidance: "Comfortable for activities year-round.", - responseType: "yes_no_na_action", - tags: ["welfare"], - }, - ], - }, - { - id: "housekeeping", - number: 24, - title: "Housekeeping and general safety", - items: [ - { - id: "hk-1", - question: "Are corridors and storage areas free from clutter?", - guidance: "Combustible storage should not block escape routes.", - responseType: "yes_no_na_action", - tags: ["housekeeping"], - }, - { - id: "hk-2", - question: "Are cleaning schedules documented and followed?", - guidance: "Include toilets, kitchen, and communal areas.", - responseType: "yes_no_na_action", - tags: ["housekeeping"], - }, - { - id: "hk-3", - question: "Is pest control managed effectively?", - guidance: "No evidence of infestation; contract in place if needed.", - responseType: "yes_no_na_action", - tags: ["housekeeping"], - }, - { - id: "hk-4", - question: "Are glass doors and low-level glazing marked or protected?", - guidance: "Manifestation at eye level to prevent impact injuries.", - responseType: "yes_no_na_action", - tags: ["housekeeping"], - }, - ], - }, - { - id: "signage", - number: 25, - title: "Signage and information", - items: [ - { - id: "sig-1", - question: "Are statutory signs displayed (fire, first aid, no smoking)?", - guidance: "Signs should be legible and up to date.", - responseType: "yes_no_na_action", - tags: ["signage"], - }, - { - id: "sig-2", - question: "Are maximum occupancy limits displayed where set?", - guidance: "Based on fire risk assessment and licensing.", - responseType: "yes_no_na_action", - tags: ["signage"], - }, - { - id: "sig-3", - question: "Is emergency contact information displayed?", - guidance: "Include NHS, gas emergency, and trustee contacts.", - responseType: "yes_no_na_action", - tags: ["signage"], - }, - { - id: "sig-4", - question: "Are premises rules and hire conditions visible to users?", - guidance: "Displayed at entrance or in hire pack.", - responseType: "yes_no_na_action", - tags: ["signage"], - }, - ], - }, + section(1, "organisation-safety", "Organisation of safety and management", "all", [ + item( + "org-1", + "Is there a designated premises manager with documented responsibilities?", + "A named volunteer should be responsible for day-to-day premises safety and maintenance.", + ["governance"], + ), + item( + "org-2", + "Does the Group Executive (trustee board) receive regular premises compliance updates?", + "Trustees should review compliance at least annually — ideally each meeting.", + ["governance"], + ), + item( + "org-3", + "Is there a current premises management plan or maintenance schedule?", + "A simple plan covering inspections, servicing, and contractor arrangements is sufficient.", + ["governance"], + ), + item( + "org-4", + "Are premises insurance documents current and stored securely?", + "Verify buildings, contents, and public liability cover with the Group treasurer.", + ["governance", "insurance"], + ), + ]), + section(2, "monitoring-incidents", "Monitoring and Incident Reporting", "all", [ + item( + "mon-1", + "Is there a system for recording accidents, incidents, and near-misses?", + "An accident book or digital log should be maintained and reviewed.", + ["incidents"], + ), + item( + "mon-2", + "Are RIDDOR-reportable incidents identified and reported where required?", + "Serious injuries and dangerous occurrences must be reported to HSE.", + ["incidents"], + ), + item( + "mon-3", + "Are incident records reviewed and actions followed up?", + "Trustees or the premises manager should review trends periodically.", + ["incidents"], + ), + item( + "mon-4", + "Is there a process for reporting safeguarding concerns?", + "Yellow Card procedures and local safeguarding contacts should be known to all leaders.", + ["incidents", "safeguarding"], + ), + ]), + section(3, "fire", "Fire", "all", [ + item( + "fire-1", + "Is a current fire risk assessment available for the premises?", + "Required for all non-domestic premises; review annually or after significant changes.", + ["fire"], + ), + item( + "fire-2", + "Are fire escape routes clearly marked, lit, and kept unobstructed?", + "Walk all escape routes during your inspection.", + ["fire"], + ), + item( + "fire-3", + "Are fire doors self-closing, undamaged, and not wedged open?", + "Check closers, seals, and signage on all fire doors.", + ["fire"], + ), + item( + "fire-4", + "Is the fire assembly point signed, known to leaders, and accessible?", + "All section leaders should know the assembly point location.", + ["fire"], + ), + ]), + section(4, "emergency-procedures", "Emergency Procedures", "all", [ + item( + "emg-1", + "Are emergency evacuation procedures documented and displayed?", + "Procedures should cover fire, gas leak, and other foreseeable emergencies.", + ["emergency"], + ), + item( + "emg-2", + "Is a fire evacuation drill carried out at least once per term?", + "Record the date, participants, and any issues identified.", + ["emergency", "fire"], + ), + item( + "emg-3", + "Are emergency contact numbers displayed (999, gas emergency, trustees)?", + "Contacts should be visible near the main entrance or telephone.", + ["emergency"], + ), + item( + "emg-4", + "Do leaders know how to raise the alarm and call the emergency services?", + "Brief new volunteers as part of premises induction.", + ["emergency"], + ), + ]), + section(5, "electrical", "Electrical", "all", [ + item( + "elec-1", + "Is there a current EICR (Electrical Installation Condition Report)?", + "Fixed-wire testing is typically required every 5 years.", + ["electrical", "eicr"], + ), + item( + "elec-2", + "Is PAT testing up to date for portable appliances?", + "Check test labels and records for all portable equipment used on site.", + ["electrical", "pat"], + ), + item( + "elec-3", + "Are consumer units accessible, labelled, and free from damage?", + "No storage in front of distribution boards; circuits should be labelled.", + ["electrical"], + ), + item( + "elec-4", + "Are extension leads and multi-way adapters used safely?", + "No daisy-chaining; avoid overloading sockets.", + ["electrical"], + ), + ]), + section(6, "gas", "Gas", "all", [ + item( + "gas-1", + "Is there a current Gas Safe certificate for all gas appliances?", + "Annual inspection by a Gas Safe registered engineer is required.", + ["gas", "profile:hasGas"], + ), + item( + "gas-2", + "Are gas appliances serviced annually with records retained?", + "Check service records for boilers, heaters, and cookers.", + ["gas", "profile:hasGas"], + ), + item( + "gas-3", + "Is the gas emergency shut-off valve accessible and labelled?", + "All users should know the location of the emergency control valve.", + ["gas", "profile:hasGas"], + ), + item( + "gas-4", + "Are ventilation requirements met for all gas appliances?", + "Check flues, vents, and room ventilation against manufacturer guidance.", + ["gas", "profile:hasGas"], + ), + ]), + section(7, "asbestos", "Asbestos Management", "all", [ + item( + "asb-1", + "Has an asbestos survey been carried out where the building pre-dates 2000?", + "Required for buildings built before 2000; check the survey report.", + ["asbestos", "profile:buildingAgeBand"], + ), + item( + "asb-2", + "Is there an asbestos management plan if asbestos-containing materials are present?", + "The plan should include monitoring, labelling, and control measures.", + ["asbestos"], + ), + item( + "asb-3", + "Are contractors informed of asbestos status before works commence?", + "Provide the asbestos register to all contractors and maintain records.", + ["asbestos"], + ), + item( + "asb-4", + "Are damaged or disturbed asbestos-containing materials reported immediately?", + "Do not disturb; seek specialist advice and restrict access.", + ["asbestos"], + ), + ]), + section(8, "water-legionella", "Water Quality (Legionella)", "all", [ + item( + "leg-1", + "Is a legionella risk assessment in place for the water system?", + "Required where stored water or infrequent use creates legionella risk.", + ["legionella", "water"], + ), + item( + "leg-2", + "Are little-used outlets flushed weekly?", + "Run taps and showers in rarely used areas for several minutes.", + ["legionella", "water"], + ), + item( + "leg-3", + "Are hot and cold water temperatures checked monthly?", + "Hot water should reach 50°C+ at outlets; cold below 20°C after flushing.", + ["legionella", "water"], + ), + item( + "leg-4", + "Is showerhead descaling carried out quarterly?", + "Descale and clean showerheads to reduce legionella risk.", + ["legionella", "water"], + ), + ]), + section(9, "third-parties", "Use by Third Parties", "all", [ + item( + "tp-1", + "Are hire agreements in place for third-party users of the premises?", + "Agreements should cover liability, safeguarding, and permitted activities.", + ["third_parties", "profile:hasThirdPartyUsers"], + ), + item( + "tp-2", + "Are third-party users given a premises induction covering safety and emergencies?", + "Include fire exits, assembly point, and any site rules.", + ["third_parties", "profile:hasThirdPartyUsers"], + ), + item( + "tp-3", + "Is insurance adequate for third-party hire activities?", + "Confirm with insurers that hire use is covered.", + ["third_parties", "insurance"], + ), + item( + "tp-4", + "Are safeguarding arrangements in place when young people may be present?", + "External groups hiring the premises must meet safeguarding requirements.", + ["third_parties", "safeguarding"], + ), + ]), + section(10, "access", "Access to the Premises", "all", [ + item( + "acc-1", + "Are access routes free from trip hazards and well maintained?", + "Check paths, steps, ramps, and car park surfaces.", + ["access"], + ), + item( + "acc-2", + "Is disabled access provided where reasonably practicable?", + "Consider ramps, door widths, toilet facilities, and parking.", + ["access"], + ), + item( + "acc-3", + "Are handrails provided and secure on stairs and ramps?", + "Check fixings, height, and continuity of handrails.", + ["access"], + ), + item( + "acc-4", + "Is emergency egress available from all occupied areas?", + "No dead ends without an alternative escape route.", + ["access", "fire"], + ), + ]), + section(11, "coshh", "Chemicals and Hazardous Substances (COSHH)", "all", [ + item( + "coshh-1", + "Are COSHH assessments available for cleaning chemicals and hazardous substances?", + "Safety data sheets should be accessible on site.", + ["coshh"], + ), + item( + "coshh-2", + "Are hazardous substances stored securely in original labelled containers?", + "No decanting into unlabelled containers.", + ["coshh"], + ), + item( + "coshh-3", + "Is appropriate PPE available where required by COSHH assessments?", + "Gloves, goggles, and other PPE as specified on safety data sheets.", + ["coshh", "ppe"], + ), + item( + "coshh-4", + "Are spill kits available in chemical storage areas?", + "Particularly relevant for kitchen and cleaning stores.", + ["coshh"], + ), + ]), + section(12, "equipment", "Equipment", "all", [ + item( + "equip-1", + "Is equipment maintained in a safe condition and fit for purpose?", + "Regular visual checks; repair or remove defective items.", + ["equipment"], + ), + item( + "equip-2", + "Are ladders and access equipment inspected and used safely?", + "Check condition, ratings, and that users are competent.", + ["equipment"], + ), + item( + "equip-3", + "Is activity equipment (e.g. pioneering, climbing) inspected before use?", + "Follow activity-specific guidance and manufacturer instructions.", + ["equipment"], + ), + item( + "equip-4", + "Is a defect reporting process in place for equipment?", + "Faulty equipment should be tagged out of use until repaired.", + ["equipment"], + ), + ]), + section(13, "flood-risk", "Flood Risk Assessment", "all", [ + item( + "fld-1", + "Has a flood risk assessment been completed for the premises?", + "Required where the site is in a flood risk zone.", + ["flood", "profile:floodRiskZone"], + ), + item( + "fld-2", + "Are flood resilience measures in place where required?", + "Flood gates, raised electrics, sandbag stores as appropriate.", + ["flood", "profile:floodRiskZone"], + ), + item( + "fld-3", + "Is there a flood emergency plan with evacuation triggers?", + "Include contact numbers and actions when flooding is forecast.", + ["flood"], + ), + item( + "fld-4", + "Are drains and gullies maintained to reduce surface water flooding?", + "Clear blockages before winter months.", + ["flood"], + ), + ]), + section(14, "staff-volunteers", "Staff and Volunteers", "extended", [ + item( + "sv-1", + "Are volunteer roles and responsibilities for premises work documented?", + "Clarify who does maintenance, inspections, and contractor liaison.", + ["staff"], + ), + item( + "sv-2", + "Are volunteers given premises safety induction before working on site?", + "Cover emergencies, hazards, and reporting procedures.", + ["staff"], + ), + item( + "sv-3", + "Is lone working managed safely for premises volunteers?", + "Check-in arrangements for out-of-hours work.", + ["staff", "lone_working"], + ), + item( + "sv-4", + "Are relevant training records maintained for premises volunteers?", + "e.g. first aid, manual handling, equipment use.", + ["staff"], + ), + ]), + section(15, "guests-visitors", "Guests and Visitors", "extended", [ + item( + "gv-1", + "Are visitor and guest procedures documented?", + "Include signing in, supervision, and emergency arrangements.", + ["guests", "profile:hasThirdPartyUsers"], + ), + item( + "gv-2", + "Is supervision adequate when young people and visitors share the premises?", + "Consider ratios and layout for mixed use.", + ["guests", "safeguarding"], + ), + item( + "gv-3", + "Are sleeping accommodation arrangements safe for guests?", + "Fire detection, evacuation, and safeguarding for overnight stays.", + ["guests", "profile:hasSleeping"], + ), + item( + "gv-4", + "Are personal emergency evacuation plans (PEEPs) in place where needed?", + "Required for individuals who may need assistance to evacuate.", + ["guests", "profile:hasSleeping", "fire"], + ), + ]), + section(16, "first-aid", "First Aid", "extended", [ + item( + "fa-1", + "Are first aid kits stocked, accessible, and checked regularly?", + "Replace used items; check against a contents checklist.", + ["first_aid"], + ), + item( + "fa-2", + "Is there a documented first aid needs assessment?", + "Consider occupancy, activities, and remoteness from medical help.", + ["first_aid"], + ), + item( + "fa-3", + "Are sufficient trained first aiders available during activities?", + "At least one first aider should be present during section meetings.", + ["first_aid"], + ), + item( + "fa-4", + "Is an accident book maintained and reviewed by trustees?", + "Record all incidents; review trends at executive meetings.", + ["first_aid", "incidents"], + ), + ]), + section(17, "contractor-management", "Contractor Management", "extended", [ + item( + "con-1", + "Is contractor insurance verified before work commences?", + "Public liability insurance minimum £5m is recommended.", + ["contractors", "profile:hasThirdPartyUsers"], + ), + item( + "con-2", + "Are hot work permits used where welding or cutting takes place?", + "Control ignition sources during maintenance works.", + ["contractors"], + ), + item( + "con-3", + "Are contractors briefed on site hazards and emergency procedures?", + "Include asbestos status, isolation points, and fire exits.", + ["contractors"], + ), + item( + "con-4", + "Is contractor work inspected and signed off on completion?", + "Retain certificates and completion records.", + ["contractors"], + ), + ]), + section(18, "safeguarding", "Safeguarding", "all", [ + item( + "sg-1", + "Are safeguarding policies displayed and accessible on site?", + "Yellow Card and safeguarding contacts should be visible.", + ["safeguarding"], + ), + item( + "sg-2", + "Are changing and toilet facilities appropriate for the age groups using the premises?", + "Consider layout, supervision, and privacy.", + ["safeguarding"], + ), + item( + "sg-3", + "Is the premises layout conducive to good safeguarding practice?", + "Avoid isolated areas without oversight or CCTV where justified.", + ["safeguarding"], + ), + item( + "sg-4", + "Are external hirers vetted for safeguarding compliance?", + "Applies when third parties use the premises.", + ["safeguarding", "profile:hasThirdPartyUsers"], + ), + ]), + section(19, "manual-handling", "Manual Handling", "extended", [ + item( + "mh-1", + "Have manual handling risks been assessed for routine premises tasks?", + "Consider furniture moves, deliveries, and maintenance activities.", + ["manual_handling"], + ), + item( + "mh-2", + "Is handling equipment (trolleys, sack trucks) available where needed?", + "Reduce lifting where reasonably practicable.", + ["manual_handling"], + ), + item( + "mh-3", + "Are volunteers briefed on safe lifting techniques?", + "Keep loads manageable; work in pairs for heavy items.", + ["manual_handling"], + ), + item( + "mh-4", + "Are storage arrangements organised to minimise awkward lifting?", + "Heavy items stored at waist height where possible.", + ["manual_handling"], + ), + ]), + section(20, "catering", "Catering", "extended", [ + item( + "cat-1", + "Is food hygiene training current for those preparing food on site?", + "Level 2 Food Safety certificate is recommended for food handlers.", + ["catering", "profile:hasCateringKitchen"], + ), + item( + "cat-2", + "Are kitchen surfaces, equipment, and storage hygienic?", + "Check cleanliness, fridge temperatures, and pest control.", + ["catering", "profile:hasCateringKitchen"], + ), + item( + "cat-3", + "Is extraction and ventilation adequate in the kitchen?", + "Canopy and filters should be cleaned regularly.", + ["catering", "profile:hasCateringKitchen"], + ), + item( + "cat-4", + "Are allergen controls in place when catering for groups?", + "Display allergen information and prevent cross-contamination.", + ["catering", "profile:hasCateringKitchen"], + ), + ]), + section(21, "sleeping-accommodation", "Sleeping Accommodation", "extended", [ + item( + "slp-1", + "Are sleeping areas provided with adequate fire detection?", + "Mains-wired smoke alarms with battery backup are preferred.", + ["sleeping", "profile:hasSleeping", "fire"], + ), + item( + "slp-2", + "Is a night-time evacuation plan documented and practised?", + "Include roles for waking young people and accounting for all occupants.", + ["sleeping", "profile:hasSleeping", "emergency"], + ), + item( + "slp-3", + "Are portable heaters prohibited in sleeping areas?", + "Fixed heating only in sleeping accommodation.", + ["sleeping", "profile:hasSleeping", "fire"], + ), + item( + "slp-4", + "Are sleeping areas segregated appropriately for safeguarding?", + "Separate facilities for different groups as required by policy.", + ["sleeping", "profile:hasSleeping", "safeguarding"], + ), + ]), + section(22, "plant-machinery", "Plant, Machinery, and Tools", "extended", [ + item( + "plt-1", + "Are risk assessments in place for plant and machinery on site?", + "Include training requirements and safe systems of work.", + ["plant", "profile:hasPlantMachinery"], + ), + item( + "plt-2", + "Are guards and safety devices in place and functional?", + "Do not operate machinery with guards removed.", + ["plant", "profile:hasPlantMachinery"], + ), + item( + "plt-3", + "Is maintenance and inspection documented for plant and machinery?", + "LOLER checks where applicable for lifting equipment.", + ["plant", "profile:hasPlantMachinery"], + ), + item( + "plt-4", + "Are only competent persons authorised to use plant and machinery?", + "Training records should be retained.", + ["plant", "profile:hasPlantMachinery"], + ), + ]), + section(23, "vehicles", "Vehicles", "extended", [ + item( + "veh-1", + "Is vehicle storage segregated from pedestrian routes?", + "Minimise interaction between vehicles and young people.", + ["vehicles", "profile:hasVehicles"], + ), + item( + "veh-2", + "Are fuel and oil stored safely with spill containment?", + "Use appropriate containers and bunding.", + ["vehicles", "profile:hasVehicles"], + ), + item( + "veh-3", + "Are minibus and trailer checks documented if applicable?", + "Daily walk-around checks and MOT/service records.", + ["vehicles", "profile:hasVehicles"], + ), + item( + "veh-4", + "Are vehicle movements managed safely during Scout activities?", + "Designate parking, reversing areas, and speed limits on site.", + ["vehicles", "profile:hasVehicles"], + ), + ]), + section(24, "ppe", "Protective Equipment (PPE)", "extended", [ + item( + "ppe-1", + "Is required PPE available, maintained, and used correctly?", + "Hard hats, gloves, eye protection as identified by risk assessments.", + ["ppe"], + ), + item( + "ppe-2", + "Are PPE requirements communicated to volunteers and contractors?", + "Include in induction and method statements.", + ["ppe"], + ), + item( + "ppe-3", + "Is PPE stored cleanly and checked before use?", + "Replace damaged or expired PPE promptly.", + ["ppe"], + ), + item( + "ppe-4", + "Is a register maintained for issued PPE where required?", + "Track issue and replacement for higher-risk activities.", + ["ppe"], + ), + ]), + section(25, "trees-grounds", "Trees and Grounds", "extended", [ + item( + "grd-1", + "Are trees inspected regularly by a competent person?", + "Annual inspection recommended; more frequent after storms.", + ["grounds", "profile:hasGrounds"], + ), + item( + "grd-2", + "Are paths, play areas, and car parks maintained safely?", + "Check for potholes, uneven surfaces, and debris.", + ["grounds", "profile:hasGrounds"], + ), + item( + "grd-3", + "Is vegetation managed to reduce fire and trip hazards?", + "Clear overgrowth from paths and building perimeters.", + ["grounds", "profile:hasGrounds"], + ), + item( + "grd-4", + "Are external waste storage areas secure and away from fire routes?", + "Bins should not obstruct escape routes.", + ["grounds", "profile:hasGrounds"], + ), + ]), ]; diff --git a/prisma/seed.ts b/prisma/seed.ts index bc7c1e8..82eebcb 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,8 +1,12 @@ -import { PrismaClient } from "@prisma/client"; +import { OrgType, Role, PrismaClient } from "@prisma/client"; +import { DEV_USER_ID } from "../lib/auth"; import { auditTemplate202509 } from "./data/audit-template-2025-09"; const prisma = new PrismaClient(); +const DEV_ORG_ID = "dev-org-3rd-southfield"; +const DEV_PREMISES_ID = "dev-premises-hut"; + async function main() { console.log("Seeding database..."); @@ -25,6 +29,53 @@ async function main() { console.log( ` Sections: ${auditTemplate202509.length}, Items: ${auditTemplate202509.reduce((n, s) => n + s.items.length, 0)}`, ); + + const organisation = await prisma.organisation.upsert({ + where: { id: DEV_ORG_ID }, + update: { + name: "3rd Southfield Scout Group", + type: OrgType.GROUP, + }, + create: { + id: DEV_ORG_ID, + name: "3rd Southfield Scout Group", + type: OrgType.GROUP, + }, + }); + + await prisma.membership.upsert({ + where: { + userId_organisationId: { + userId: DEV_USER_ID, + organisationId: organisation.id, + }, + }, + update: { role: Role.ADMIN }, + create: { + userId: DEV_USER_ID, + organisationId: organisation.id, + role: Role.ADMIN, + }, + }); + + const premises = await prisma.premises.upsert({ + where: { id: DEV_PREMISES_ID }, + update: { + name: "3rd Southfield Scout Hut", + address: "12 Oak Lane, Southfield, SF1 2AB", + organisationId: organisation.id, + }, + create: { + id: DEV_PREMISES_ID, + name: "3rd Southfield Scout Hut", + address: "12 Oak Lane, Southfield, SF1 2AB", + organisationId: organisation.id, + }, + }); + + console.log(`Dev organisation: ${organisation.name} (${organisation.id})`); + console.log(`Dev premises: ${premises.name} (${premises.id})`); + console.log(` Profile wizard: /premises/${premises.id}/profile`); } main() diff --git a/types/audit-template.ts b/types/audit-template.ts index b1be4b3..23a2e02 100644 --- a/types/audit-template.ts +++ b/types/audit-template.ts @@ -6,10 +6,13 @@ export type AuditTemplateItem = { tags: string[]; }; +export type AuditSectionScope = "all" | "extended"; + export type AuditTemplateSection = { id: string; number: number; title: string; + scope: AuditSectionScope; items: AuditTemplateItem[]; }; diff --git a/types/index.ts b/types/index.ts index 5efb5a1..770c40e 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1,5 +1,7 @@ export type { AuditTemplateItem, AuditTemplateSection, AuditTemplateSections } from "./audit-template"; +export type { ProfileForSections } from "@/lib/sections"; + export { OrgType, Role,