Add OSHA Form 301 and Osha page components
CI / Lint, typecheck, test, build (push) Canceled after 0s
CI / End-to-end tests (push) Canceled after 0s

- Implemented Form301 component for displaying OSHA Form 301 equivalent, including employee and case information.
- Created Osha component to manage OSHA recordkeeping, including tabs for 300 Log, 300A Summary, Hours & rates, and Submit to OSHA.
- Added functionality for printing and downloading CSV files for OSHA submissions.
- Integrated loading and error handling states for data fetching.
This commit is contained in:
jayjennings committed 2026-10-06 13:09:57 -07:00
1 parent 3e964d1176
commit 61c876edec
59 files changed
+9180 -183

No files matched your search

+27 -1
View File
@@ -2,7 +2,8 @@ import { and, eq, inArray, isNotNull, isNull, lt, lte, ne, or } from 'drizzle-or
import { incidentReference } from '../shared/format.ts'
import type { Config } from './config.ts'
import type { Db } from './db/client.ts'
import { correctiveActions, incidents, users } from './db/schema.ts'
import { OSHA_REPORTING_CONTACT, severeInjuryReport } from '../shared/osha.ts'
import { correctiveActions, incidents, injuredPersons, users } from './db/schema.ts'
import { addDays, localDate } from './lib/dates.ts'
import type { Mailer } from './mail.ts'
@@ -93,6 +94,31 @@ export class Notifier {
})
}
/** 1904.39: fatalities must be reported to OSHA within 8 hours; hospitalizations, amputations and eye losses within 24. */
severeInjury(caseId: string) {
this.dispatch(async () => {
const [row] = await this.db
.select({ person: injuredPersons, incident: incidents })
.from(injuredPersons)
.innerJoin(incidents, eq(incidents.id, injuredPersons.incidentId))
.where(eq(injuredPersons.id, caseId))
const report = row && severeInjuryReport(row.person)
if (!report) return
const recipients = await this.db
.select({ email: users.email })
.from(users)
.where(and(eq(users.organizationId, row.incident.organizationId), eq(users.active, true), inArray(users.role, ['safety_manager', 'admin'])))
const reference = incidentReference(row.incident.number)
for (const { email } of recipients) {
await this.mailer.send({
to: email,
subject: `Report to OSHA within ${report.withinHours} hours: ${reference}`,
text: `${reference} (${row.incident.title}) was recorded with: ${report.reasons.join(', ')}. OSHA requires a direct report within ${report.withinHours} hours of learning about it (29 CFR 1904.39).\n\n${OSHA_REPORTING_CONTACT}\n\nAfter reporting, record the date and time on the case: ${this.link(`/incidents/${row.incident.number}`)}`,
})
}
})
}
actionAssigned(actionId: string, assignedBy: string) {
this.dispatch(async () => {
const [row] = await this.db