Triage Incident
{incident.reference_no ?? id}
{incident.reference_no ?? id}
{(incident as { reference_no: string | null }).reference_no ?? id}
| Ref | Action | Owner | Due | Priority | Status |
|---|---|---|---|---|---|
| {(c.incidents as { reference_no: string | null } | null)?.reference_no ?? '—'} | {c.description} | {(c.owner as { name: string } | null)?.name ?? '—'} | {c.due_date} | {c.priority} | {STATUS_LABELS[c.status]} |
{c.description}
Due {c.due_date}
))} {byStatus[status].length === 0 && (Empty
)}{(incident as { reference_no: string | null }).reference_no ?? id}
Incident: {(capa.incidents as unknown as { reference_no: string | null } | null)?.reference_no ?? '—'}
{(capa as { description: string }).description}
Owner
{(capa.owner as unknown as { name: string } | null)?.name ?? '—'}
Department
{(capa as { department: string }).department}
Due Date
{(capa as { due_date: string }).due_date}
Status
{status.replace(/_/g, ' ')}
Root Cause Reference
{(capa as { root_cause_ref: string }).root_cause_ref}
Completed: {new Date((capa as { completed_at: string }).completed_at).toLocaleString('en-MY', { timeZone: 'Asia/Kuala_Lumpur' })}
)}Hi ${ownerName},
CAPA action for incident ${incidentRef} requires attention.
Action: ${(capa as { description: string }).description}
Due date: ${(capa as { due_date: string }).due_date}
` const text = `CAPA ${incidentRef}: ${(capa as { description: string }).description}\nDue: ${(capa as { due_date: string }).due_date}\n${capaUrl}` const to = [ownerEmail] const siteId = (capa.incidents as unknown as { site_id: string } | null)?.site_id if (siteId && ['due_today', 'overdue_3d', 'overdue_7d'].includes(threshold)) { const { data: hseUsers } = await supabase .from('users') .select('email') .eq('site_id', siteId) .in('role', ['hse', 'supervisor', 'management']) if (hseUsers) to.push(...hseUsers.map((u: { email: string }) => u.email)) } const { error } = await resend.emails.send({ from, to: [...new Set(to)], subject, html, text }) if (error) { console.error('Escalation email error:', error) continue } await supabase.from('notifications_log').insert({ capa_id: capa.id, channel: 'email', recipient: to.join(','), status: threshold, }) notified++ } return { notified } } ``` - [ ] **Step 4: Run tests to confirm they pass** ```bash npx vitest run tests/lib/notifications/capa-escalation.test.ts ``` Expected: `6 passed` - [ ] **Step 5: Create cron API route** Create `app/api/cron/capa-escalation/route.ts`: ```typescript export const dynamic = 'force-dynamic' import { NextRequest, NextResponse } from 'next/server' import { createClient } from '@/lib/supabase/server' import { escalateOverdueCapa } from '@/lib/notifications/capa-escalation' export async function GET(request: NextRequest) { const auth = request.headers.get('authorization') const expected = `Bearer ${process.env.CRON_SECRET}` if (!auth || auth !== expected) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } const supabase = await createClient() const { notified } = await escalateOverdueCapa(supabase) return NextResponse.json({ ok: true, notified }) } ``` - [ ] **Step 6: Add CRON_SECRET to .env.local.example** Open `.env.local.example` and add at the bottom: ``` CRON_SECRET=