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

+38
View File
@@ -0,0 +1,38 @@
import { expect, test } from '@playwright/test'
import { signIn, unique } from './fixtures'
test('shows leading and lagging indicators with charts that have table views', async ({ page }) => {
await signIn(page, 'safety_manager')
await page.goto('/analytics?range=365')
await expect(page.getByRole('heading', { name: 'Leading indicators' })).toBeVisible()
await expect(page.getByText('Hazards & near misses reported')).toBeVisible()
await expect(page.getByRole('heading', { name: 'Lagging indicators' })).toBeVisible()
// Every chart has a table view
const trend = page.locator('figure').filter({ hasText: 'Reports per' })
await trend.getByRole('button', { name: 'Table' }).click()
await expect(trend.getByRole('columnheader', { name: 'Hazards & near misses' })).toBeVisible()
})
test('filters and saved views', async ({ page, isMobile }) => {
test.skip(isMobile, 'Covered on desktop')
await signIn(page, 'supervisor')
await page.goto('/analytics')
await page.getByLabel('Date range').selectOption('30')
await page.getByLabel('Incident type').selectOption('hazard')
await expect(page).toHaveURL(/range=30/)
await expect(page).toHaveURL(/type=hazard/)
const name = unique('Hazards this month')
await page.getByRole('button', { name: 'Save view' }).click()
await page.getByLabel('View name').fill(name)
await page.getByRole('button', { name: 'Save', exact: true }).click()
await expect(page.getByLabel('Saved views')).toHaveValue(/.+/)
// Clearing the filters and picking the view brings them back
await page.goto('/analytics')
await expect(page.getByLabel('Incident type')).toHaveValue('')
await page.getByLabel('Saved views').selectOption({ label: name })
await expect(page.getByLabel('Incident type')).toHaveValue('hazard')
await expect(page.getByLabel('Date range')).toHaveValue('30')
})
+1
View File
@@ -5,6 +5,7 @@ export const users = {
admin: { name: 'Avery Admin', email: 'admin@e2e.test' },
supervisor: { name: 'Sam Supervisor', email: 'supervisor@e2e.test' },
reporter: { name: 'Riley Reporter', email: 'reporter@e2e.test' },
safety_manager: { name: 'Morgan Manager', email: 'manager@e2e.test' },
} as const
export async function signIn(page: Page, who: keyof typeof users) {
+1 -1
View File
@@ -18,7 +18,7 @@ export default async function globalSetup() {
process.env.E2E_KIOSK_TOKEN = kiosk.token
process.env.E2E_SITE_ID = site.id
process.env.E2E_AREA_ID = area.id
for (const role of ['supervisor', 'reporter'] as const) {
for (const role of ['supervisor', 'reporter', 'safety_manager'] as const) {
await ok(await api.post('/api/users', { data: { ...users[role], role, password: PASSWORD } }))
}
await api.dispose()
+64
View File
@@ -0,0 +1,64 @@
import { expect, test } from '@playwright/test'
import { signIn, unique } from './fixtures'
test('records an injury on the OSHA log and exports it', async ({ page, browser, isMobile }) => {
test.skip(isMobile, 'Recordkeeping is a desktop task')
test.setTimeout(60_000)
const title = unique('Burn from hot press platen')
// A supervisor reports it; they can't see medical details
const supervisor = await (await browser.newContext()).newPage()
await signIn(supervisor, 'supervisor')
await supervisor.goto('/report')
await supervisor.getByLabel('Incident title').fill(title)
await supervisor.getByLabel('Incident type').selectOption('injury')
await supervisor.getByRole('button', { name: 'Submit report' }).click()
await expect(supervisor.getByRole('heading', { level: 1 })).toHaveText(title)
const url = supervisor.url()
// The safety manager decides recordability
await signIn(page, 'safety_manager')
await page.goto(url)
await page.getByRole('button', { name: 'Add person' }).click()
await page.getByLabel('Name', { exact: true }).fill('Casey Worker')
await page.getByPlaceholder('e.g. Press operator').fill('Press operator')
const verdict = page.locator('[aria-live="polite"]')
await expect(verdict).toContainText('Answer the questions above')
await page.getByText('Is it work-related?').locator('..').getByLabel('Yes').check()
await page.getByText('Is it a new case?').locator('..').getByLabel('Yes').check()
await expect(verdict).toContainText('Not recordable') // first aid only
await page.getByText('Days away from work (count').click()
await page.getByLabel('Days away so far').fill('3')
await expect(verdict).toContainText('Recordable: Days away from work')
await page.getByText('Hospitalized as an in-patient').click()
await expect(page.getByText('Must be reported to OSHA within 24 hours.')).toBeVisible()
await page.getByPlaceholder('e.g. Laceration, sprain, burn').fill('Second-degree burn')
await page.getByRole('button', { name: 'Add person' }).click()
await expect(page.getByText('OSHA recordable · Days away from work')).toBeVisible()
await expect(page.getByRole('alert').filter({ hasText: 'Report to OSHA within 24 hours' })).toBeVisible()
// Supervisors see that someone was hurt, nothing more
await supervisor.reload()
await expect(supervisor.getByText('1 person recorded. Injury and medical details are confidential')).toBeVisible()
await expect(supervisor.getByText('Casey Worker')).toHaveCount(0)
// It's on the 300 Log, and the 301 is one click away
await page.getByRole('link', { name: 'OSHA log' }).click()
const row = page.getByRole('row').filter({ hasText: 'Casey Worker' })
await expect(row).toContainText('Second-degree burn')
await expect(row).toContainText('Missing:')
await page.getByRole('tab', { name: 'Submit to OSHA' }).click()
await expect(page.getByText('Enter hours worked for each month.')).toBeVisible()
const download = page.waitForEvent('download')
await page.getByRole('link', { name: /300A summary CSV/ }).click()
expect((await download).suggestedFilename()).toMatch(/^osha-ita-300a-\d{4}\.csv$/)
})
test("OSHA pages aren't offered to supervisors", async ({ page, isMobile }) => {
await signIn(page, 'supervisor')
if (isMobile) await page.getByRole('button', { name: 'Open menu' }).click()
await expect(page.getByRole('link', { name: 'Incidents' }).first()).toBeVisible()
await expect(page.getByRole('link', { name: 'OSHA log' })).toHaveCount(0)
})