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

+156 -11
View File
@@ -1,9 +1,14 @@
// Response shapes returned by the API. Dates are ISO 8601 strings.
import type { SummaryTotals } from './osha.ts'
import type {
actionKinds,
actionStatuses,
AreaKind,
employmentTypes,
IncidentStatus,
OshaCaseType,
OshaOutcome,
OshaSpecialCase,
IncidentType,
rcaMethods,
Role,
@@ -45,7 +50,18 @@ export interface Area {
archivedAt: string | null
}
export interface Site {
export interface Establishment {
streetAddress: string | null
city: string | null
state: string | null
zip: string | null
naicsCode: string | null
industryDescription: string | null
ein: string | null
establishmentType: number
}
export interface Site extends Partial<Establishment> {
id: string
name: string
address: string | null
@@ -153,8 +169,10 @@ export interface IncidentDetail extends Incident {
actions: CorrectiveAction[]
attachments: Attachment[]
transitions: TransitionOption[]
/** Number of injured or ill people recorded; details are only for safety managers. */
injuredCount: number
/** What the signed-in user may do on this incident. */
can: { edit: boolean; manageInvestigation: boolean; manageActions: boolean }
can: { edit: boolean; manageInvestigation: boolean; manageActions: boolean; manageOsha: boolean }
}
export interface Comment {
@@ -193,13 +211,140 @@ export interface OverviewStats {
bySite: { siteId: string; name: string; count: number }[]
}
export interface AnalyticsStats {
periodDays: number
total: number
byType: { type: IncidentType; count: number }[]
bySeverity: { severity: Severity; count: number }[]
topRootCause: { category: RootCauseCategory; count: number; share: number } | null
/** Share of completed actions finished on or before their due date. */
onTimeActionRate: number | null
monthlyCompletedActions: { month: string; onTime: number; late: number }[]
/** A number for the selected period and the same-length period before it. */
export interface Kpi {
value: number | null
previous: number | null
}
/** Incident types grouped for trend charts. */
export type TypeGroup = 'hazards' | 'injuries' | 'damage'
export interface AnalyticsStats {
from: string
to: string
previousFrom: string
bucket: 'week' | 'month'
kpis: {
reports: Kpi
/** Leading: hazards and near misses reported. */
leadingReports: Kpi
injuries: Kpi
/** Leading: hazards and near misses per injury or illness. */
nearMissRatio: Kpi
/** Leading: share of reports moved out of "reported" within 24 hours. */
triagedWithin24h: Kpi
/** Leading: share of actions completed by their due date. */
actionsOnTime: Kpi
averageDaysToClose: Kpi
/** Lagging: OSHA-recordable cases (supervisors and above). */
recordables: Kpi | null
overdueActions: number
}
trend: ({ start: string } & Record<TypeGroup, number>)[]
byLocation: { id: string | null; name: string; count: number }[]
byRootCause: { category: RootCauseCategory; count: number }[]
/** heatmap[weekday][hour], weekday 0 = Monday, in the server's time zone. */
heatmap: number[][]
actionsByMonth: { month: string; onTime: number; late: number }[]
/** Rolling 12-month OSHA rates at each month end; null for reporters. */
rates: { month: string; trir: number | null; dart: number | null }[] | null
yearly: { year: number; recordables: number; dartCases: number; hours: number; trir: number | null; dart: number | null }[] | null
}
export interface SavedView {
id: string
page: 'analytics' | 'incidents'
name: string
filters: Record<string, string>
shared: boolean
mine: boolean
}
// ---- OSHA recordkeeping ----
export interface OshaCase {
id: string
incident: { id: string; number: number; reference: string; title: string; occurredAt: string }
caseNumber: string
name: string
jobTitle: string | null
employmentType: (typeof employmentTypes)[number]
bodyPart: string | null
natureOfInjury: string | null
caseType: OshaCaseType
workRelated: boolean | null
newCase: boolean | null
died: boolean
dateOfDeath: string | null
daysAwayFromWork: boolean
daysAway: number
restrictedOrTransfer: boolean
daysRestricted: number
medicalTreatment: boolean
lossOfConsciousness: boolean
significantDiagnosis: boolean
specialCase: OshaSpecialCase
privacyCase: boolean
recordable: boolean
outcome: OshaOutcome | null
inpatientHospitalization: boolean
amputation: boolean
lossOfEye: boolean
reportedToOshaAt: string | null
/** Set when 1904.39 requires a direct report to OSHA. */
severeReport: { withinHours: 8 | 24; reasons: string[]; dueBy: string } | null
employeeAddress: string | null
dateOfBirth: string | null
dateOfHire: string | null
sex: 'M' | 'F' | null
physicianName: string | null
treatmentFacility: string | null
treatedInEmergencyRoom: boolean
timeStartedWork: string | null
activityBefore: string | null
whatHappened: string | null
objectSubstance: string | null
/** Form 301 / ITA fields still empty for a recordable case. */
missingFields: string[]
updatedAt: string
}
/** One line of the Form 300 Log. */
export interface OshaLogRow {
caseId: string
caseNumber: string
incidentNumber: number
/** "Privacy case" for privacy concern cases. */
employeeName: string
jobTitle: string
dateOfInjury: string
location: string
description: string
outcome: OshaOutcome
daysAway: number
daysRestricted: number
caseType: OshaCaseType
missingFields: string[]
}
export interface OshaYear {
site: { id: string; name: string } & Establishment
companyName: string
year: number
rows: OshaLogRow[]
totals: SummaryTotals
months: { month: number; hours: number | null; averageEmployees: number | null }[]
totalHoursWorked: number
annualAverageEmployees: number
maxEmployees: number
rates: { trir: number | null; dart: number | null; ltir: number | null }
checks: { errors: string[]; warnings: string[] }
missingEstablishmentFields: string[]
}
export interface Form301 {
case: OshaCase
establishment: { name: string; companyName: string } & Establishment
incident: { reference: string; occurredAt: string; title: string; description: string | null; site: string; area: string | null; locationDetail: string | null }
}