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

+71
View File
@@ -14,6 +14,9 @@ export const actionKindEnum = pgEnum('action_kind', vocab.actionKinds)
export const actionStatusEnum = pgEnum('action_status', vocab.actionStatuses)
export const authTokenPurposeEnum = pgEnum('auth_token_purpose', ['magic_link', 'password_reset'])
export const reportChannelEnum = pgEnum('report_channel', ['web', 'kiosk'])
export const oshaCaseTypeEnum = pgEnum('osha_case_type', vocab.oshaCaseTypes)
export const oshaOutcomeEnum = pgEnum('osha_outcome', vocab.oshaOutcomes)
export const oshaSpecialCaseEnum = pgEnum('osha_special_case', vocab.oshaSpecialCases)
const id = () => uuid().primaryKey().defaultRandom()
const timestamptz = () => timestamp({ withTimezone: true })
@@ -91,6 +94,16 @@ export const sites = pgTable(
name: text().notNull(),
address: text(),
timezone: text().notNull().default('UTC'),
// OSHA establishment details (Form 300A and the ITA upload)
streetAddress: text(),
city: text(),
state: text(),
zip: text(),
naicsCode: text(),
industryDescription: text(),
ein: text(),
/** ITA establishment_type: 1 private, 2 state government, 3 local government. */
establishmentType: integer().notNull().default(1),
archivedAt: timestamptz(),
createdAt: createdAt(),
updatedAt: updatedAt(),
@@ -208,6 +221,42 @@ export const injuredPersons = pgTable(
daysRestricted: integer().notNull().default(0),
/** OSHA privacy case: name withheld from the 300 log. */
privacyCase: boolean().notNull().default(false),
// OSHA recordability (29 CFR 1904). Null answers mean "not assessed yet".
caseNumber: text(),
workRelated: boolean(),
newCase: boolean(),
died: boolean().notNull().default(false),
dateOfDeath: date({ mode: 'string' }),
daysAwayFromWork: boolean().notNull().default(false),
restrictedOrTransfer: boolean().notNull().default(false),
medicalTreatment: boolean().notNull().default(false),
lossOfConsciousness: boolean().notNull().default(false),
significantDiagnosis: boolean().notNull().default(false),
specialCase: oshaSpecialCaseEnum().notNull().default('none'),
caseType: oshaCaseTypeEnum().notNull().default('injury'),
/** Derived from the answers above with shared/osha.ts, stored for reporting. */
recordable: boolean().notNull().default(false),
outcome: oshaOutcomeEnum(),
// Severe injury reporting (1904.39)
inpatientHospitalization: boolean().notNull().default(false),
amputation: boolean().notNull().default(false),
lossOfEye: boolean().notNull().default(false),
reportedToOshaAt: timestamptz(),
// Form 301 details
employeeAddress: text(),
dateOfBirth: date({ mode: 'string' }),
dateOfHire: date({ mode: 'string' }),
sex: text(),
physicianName: text(),
treatmentFacility: text(),
treatedInEmergencyRoom: boolean().notNull().default(false),
timeStartedWork: text(),
activityBefore: text(),
whatHappened: text(),
objectSubstance: text(),
createdAt: createdAt(),
updatedAt: updatedAt(),
},
@@ -330,8 +379,30 @@ export const hoursWorked = pgTable(
/** First day of the month, e.g. `2026-10-01`. */
month: date({ mode: 'string' }).notNull(),
hours: numeric({ precision: 12, scale: 2 }).notNull(),
/** Average number of employees on the payroll that month (for the annual average). */
averageEmployees: integer(),
createdAt: createdAt(),
updatedAt: updatedAt(),
},
(t) => [uniqueIndex('hours_worked_site_month_unique').on(t.siteId, t.month)],
)
/** Named filter sets for the Analytics and Incidents pages; private, or shared with the organization. */
export const savedViews = pgTable(
'saved_views',
{
id: id(),
organizationId: uuid()
.notNull()
.references(() => organizations.id),
userId: uuid()
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
page: text().notNull(),
name: text().notNull(),
filters: jsonb().$type<Record<string, string>>().notNull(),
shared: boolean().notNull().default(false),
createdAt: createdAt(),
},
(t) => [index('saved_views_organization_page_idx').on(t.organizationId, t.page)],
)