From 56ac6b04c0a5f06211004f31c8800bdd3c6172ad Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Thu, 26 Mar 2026 16:40:35 -0700
Subject: [PATCH 01/10] feat: state machine and skeleton ui
---
.../AddEmployeesHoliday.tsx | 29 +
.../AddEmployeesToPolicy.tsx | 29 +
.../HolidaySelectionForm.tsx | 29 +
.../PolicyDetailsForm/PolicyDetailsForm.tsx | 38 ++
.../TimeOff/PolicyList/PolicyList.tsx | 39 ++
.../TimeOff/PolicySettings/PolicySettings.tsx | 29 +
.../PolicyTypeSelector/PolicyTypeSelector.tsx | 44 ++
.../TimeOff/TimeOffFlow/TimeOffFlow.tsx | 23 +
.../TimeOffFlow/TimeOffFlowComponents.tsx | 150 +++++
src/components/TimeOff/TimeOffFlow/index.ts | 2 +
.../TimeOffFlow/timeOffStateMachine.test.ts | 562 ++++++++++++++++++
.../TimeOffFlow/timeOffStateMachine.ts | 359 +++++++++++
.../ViewHolidayEmployees.tsx | 22 +
.../ViewHolidaySchedule.tsx | 22 +
.../ViewPolicyDetails/ViewPolicyDetails.tsx | 22 +
.../ViewPolicyEmployees.tsx | 22 +
src/components/TimeOff/index.ts | 24 +
17 files changed, 1445 insertions(+)
create mode 100644 src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
create mode 100644 src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
create mode 100644 src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
create mode 100644 src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
create mode 100644 src/components/TimeOff/PolicyList/PolicyList.tsx
create mode 100644 src/components/TimeOff/PolicySettings/PolicySettings.tsx
create mode 100644 src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
create mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
create mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
create mode 100644 src/components/TimeOff/TimeOffFlow/index.ts
create mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
create mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
create mode 100644 src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
create mode 100644 src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
create mode 100644 src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
create mode 100644 src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
create mode 100644 src/components/TimeOff/index.ts
diff --git a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
new file mode 100644
index 000000000..fdda6176e
--- /dev/null
+++ b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface AddEmployeesHolidayProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function AddEmployeesHoliday(props: AddEmployeesHolidayProps) {
+ return (
+
+
+
Add Employees to Holiday (companyId: {props.companyId})
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
new file mode 100644
index 000000000..d9b1f970a
--- /dev/null
+++ b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface AddEmployeesToPolicyProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function AddEmployeesToPolicy(props: AddEmployeesToPolicyProps) {
+ return (
+
+
+
Add Employees to Policy + Starting Balances (policyId: {props.policyId})
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
new file mode 100644
index 000000000..00603a872
--- /dev/null
+++ b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface HolidaySelectionFormProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function HolidaySelectionForm(props: HolidaySelectionFormProps) {
+ return (
+
+
+
Holiday Selection Form (companyId: {props.companyId})
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
new file mode 100644
index 000000000..0e796cdf6
--- /dev/null
+++ b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
@@ -0,0 +1,38 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicyDetailsFormProps extends BaseComponentInterface {
+ companyId: string
+ policyType: 'sick' | 'vacation'
+}
+
+export function PolicyDetailsForm(props: PolicyDetailsFormProps) {
+ return (
+
+
+
+ Policy Details Form (type: {props.policyType}, companyId: {props.companyId})
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyList/PolicyList.tsx b/src/components/TimeOff/PolicyList/PolicyList.tsx
new file mode 100644
index 000000000..8e2f5afd5
--- /dev/null
+++ b/src/components/TimeOff/PolicyList/PolicyList.tsx
@@ -0,0 +1,39 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicyListProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function PolicyList(props: PolicyListProps) {
+ return (
+
+
+
Policy List (companyId: {props.companyId})
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicySettings/PolicySettings.tsx b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
new file mode 100644
index 000000000..c05d05d8e
--- /dev/null
+++ b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicySettingsProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function PolicySettings(props: PolicySettingsProps) {
+ return (
+
+
+
Policy Settings (policyId: {props.policyId})
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
new file mode 100644
index 000000000..792fa8c55
--- /dev/null
+++ b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
@@ -0,0 +1,44 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicyTypeSelectorProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function PolicyTypeSelector(props: PolicyTypeSelectorProps) {
+ return (
+
+
+
Policy Type Selector (companyId: {props.companyId})
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
new file mode 100644
index 000000000..5b22ec216
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
@@ -0,0 +1,23 @@
+import { createMachine } from 'robot3'
+import { useMemo } from 'react'
+import { timeOffMachine } from './timeOffStateMachine'
+import type { TimeOffFlowProps, TimeOffFlowContextInterface } from './TimeOffFlowComponents'
+import { PolicyListContextual } from './TimeOffFlowComponents'
+import { Flow } from '@/components/Flow/Flow'
+
+export const TimeOffFlow = ({ companyId, onEvent }: TimeOffFlowProps) => {
+ const timeOffFlow = useMemo(
+ () =>
+ createMachine(
+ 'policyList',
+ timeOffMachine,
+ (initialContext: TimeOffFlowContextInterface) => ({
+ ...initialContext,
+ component: PolicyListContextual,
+ companyId,
+ }),
+ ),
+ [companyId],
+ )
+ return
+}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
new file mode 100644
index 000000000..b9c6ca237
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
@@ -0,0 +1,150 @@
+import type { ReactNode } from 'react'
+import { PolicyList } from '../PolicyList/PolicyList'
+import { PolicyTypeSelector } from '../PolicyTypeSelector/PolicyTypeSelector'
+import { PolicyDetailsForm } from '../PolicyDetailsForm/PolicyDetailsForm'
+import { PolicySettings } from '../PolicySettings/PolicySettings'
+import { AddEmployeesToPolicy } from '../AddEmployeesToPolicy/AddEmployeesToPolicy'
+import { ViewPolicyDetails } from '../ViewPolicyDetails/ViewPolicyDetails'
+import { ViewPolicyEmployees } from '../ViewPolicyEmployees/ViewPolicyEmployees'
+import { HolidaySelectionForm } from '../HolidaySelectionForm/HolidaySelectionForm'
+import { AddEmployeesHoliday } from '../AddEmployeesHoliday/AddEmployeesHoliday'
+import { ViewHolidayEmployees } from '../ViewHolidayEmployees/ViewHolidayEmployees'
+import { ViewHolidaySchedule } from '../ViewHolidaySchedule/ViewHolidaySchedule'
+import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow'
+import type { BaseComponentInterface } from '@/components/Base'
+import { Flex } from '@/components/Common'
+import { ensureRequired } from '@/helpers/ensureRequired'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+
+export interface TimeOffFlowProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export type TimeOffFlowAlert = {
+ type: 'error' | 'info' | 'success'
+ title: string
+ content?: ReactNode
+}
+
+export type TimeOffPolicyType = 'sick' | 'vacation' | 'holiday'
+
+export interface TimeOffFlowContextInterface extends FlowContextInterface {
+ companyId: string
+ policyType?: TimeOffPolicyType
+ policyId?: string
+ alerts?: TimeOffFlowAlert[]
+}
+
+export function PolicyListContextual() {
+ const { onEvent, companyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function PolicyTypeSelectorContextual() {
+ const { onEvent, companyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function PolicyDetailsFormContextual() {
+ const { onEvent, companyId, policyType, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function PolicySettingsContextual() {
+ const { onEvent, policyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function AddEmployeesToPolicyContextual() {
+ const { onEvent, policyId } = useFlow()
+ return
+}
+
+export function ViewPolicyDetailsContextual() {
+ const { onEvent, policyId } = useFlow()
+ return
+}
+
+export function ViewPolicyEmployeesContextual() {
+ const { onEvent, policyId } = useFlow()
+ return
+}
+
+export function HolidaySelectionFormContextual() {
+ const { onEvent, companyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function AddEmployeesHolidayContextual() {
+ const { onEvent, companyId } = useFlow()
+ return
+}
+
+export function ViewHolidayEmployeesContextual() {
+ const { onEvent, companyId } = useFlow()
+ return
+}
+
+export function ViewHolidayScheduleContextual() {
+ const { onEvent, companyId } = useFlow()
+ return
+}
diff --git a/src/components/TimeOff/TimeOffFlow/index.ts b/src/components/TimeOff/TimeOffFlow/index.ts
new file mode 100644
index 000000000..db1e7c9b2
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/index.ts
@@ -0,0 +1,2 @@
+export { TimeOffFlow } from './TimeOffFlow'
+export type { TimeOffFlowProps } from './TimeOffFlowComponents'
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
new file mode 100644
index 000000000..a4268c98c
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
@@ -0,0 +1,562 @@
+import { describe, expect, it } from 'vitest'
+import { createMachine, interpret, type SendFunction } from 'robot3'
+import { timeOffMachine } from './timeOffStateMachine'
+import type { TimeOffFlowContextInterface } from './TimeOffFlowComponents'
+import { componentEvents } from '@/shared/constants'
+
+type TimeOffState =
+ | 'policyList'
+ | 'policyTypeSelector'
+ | 'policyDetailsForm'
+ | 'policySettings'
+ | 'addEmployeesToPolicy'
+ | 'viewPolicyDetails'
+ | 'viewPolicyEmployees'
+ | 'holidaySelectionForm'
+ | 'addEmployeesHoliday'
+ | 'viewHolidayEmployees'
+ | 'viewHolidaySchedule'
+ | 'final'
+
+function createTestMachine(initialState: TimeOffState = 'policyList') {
+ return createMachine(
+ initialState,
+ timeOffMachine,
+ (initialContext: TimeOffFlowContextInterface) => ({
+ ...initialContext,
+ component: () => null,
+ companyId: 'company-123',
+ }),
+ )
+}
+
+function createService(initialState: TimeOffState = 'policyList') {
+ const machine = createTestMachine(initialState)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return interpret(machine, () => {}, {} as any)
+}
+
+function send(service: ReturnType, type: string, payload?: unknown) {
+ ;(service.send as SendFunction)({ type, payload })
+}
+
+function toPolicyTypeSelector(service: ReturnType) {
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ expect(service.machine.current).toBe('policyTypeSelector')
+}
+
+function toPolicyDetailsForm(service: ReturnType) {
+ toPolicyTypeSelector(service)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+}
+
+function toPolicySettings(service: ReturnType) {
+ toPolicyDetailsForm(service)
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
+ expect(service.machine.current).toBe('policySettings')
+}
+
+function toAddEmployeesToPolicy(service: ReturnType) {
+ toPolicySettings(service)
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+ expect(service.machine.current).toBe('addEmployeesToPolicy')
+}
+
+function toHolidaySelectionForm(service: ReturnType) {
+ toPolicyTypeSelector(service)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
+ expect(service.machine.current).toBe('holidaySelectionForm')
+}
+
+function toAddEmployeesHoliday(service: ReturnType) {
+ toHolidaySelectionForm(service)
+ send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+ expect(service.machine.current).toBe('addEmployeesHoliday')
+}
+
+describe('timeOffStateMachine', () => {
+ describe('policyList state', () => {
+ it('starts in policyList by default', () => {
+ const service = createService()
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('transitions to policyTypeSelector on TIME_OFF_CREATE_POLICY', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to viewPolicyDetails on TIME_OFF_VIEW_POLICY with sick/vacation type', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-456',
+ policyType: 'vacation',
+ })
+
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ expect(service.context.policyId).toBe('policy-456')
+ expect(service.context.policyType).toBe('vacation')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to viewHolidayEmployees on TIME_OFF_VIEW_POLICY with holiday type', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-789',
+ policyType: 'holiday',
+ })
+
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ expect(service.context.policyId).toBe('policy-789')
+ expect(service.context.policyType).toBe('holiday')
+ expect(service.context.alerts).toBeUndefined()
+ })
+ })
+
+ describe('policyTypeSelector state', () => {
+ it('transitions to policyDetailsForm on sick type selection', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
+
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.policyType).toBe('sick')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to policyDetailsForm on vacation type selection', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.policyType).toBe('vacation')
+ })
+
+ it('transitions to holidaySelectionForm on holiday type selection', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
+
+ expect(service.machine.current).toBe('holidaySelectionForm')
+ expect(service.context.policyType).toBe('holiday')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to policyList on CANCEL', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+ })
+
+ describe('sick/vacation creation flow', () => {
+ it('transitions policyDetailsForm -> policySettings on POLICY_DETAILS_DONE', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
+
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.policyId).toBe('policy-123')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions policySettings -> addEmployeesToPolicy on POLICY_SETTINGS_DONE', () => {
+ const service = createService()
+ toPolicySettings(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+
+ expect(service.machine.current).toBe('addEmployeesToPolicy')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions addEmployeesToPolicy -> viewPolicyDetails on ADD_EMPLOYEES_DONE', () => {
+ const service = createService()
+ toAddEmployeesToPolicy(service)
+
+ send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
+
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('supports full happy path: policyList -> viewPolicyDetails', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ expect(service.machine.current).toBe('policyTypeSelector')
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.policyType).toBe('vacation')
+
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.policyId).toBe('policy-123')
+
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+ expect(service.machine.current).toBe('addEmployeesToPolicy')
+
+ send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ expect(service.context.policyId).toBe('policy-123')
+ })
+ })
+
+ describe('holiday creation flow', () => {
+ it('transitions holidaySelectionForm -> addEmployeesHoliday on HOLIDAY_SELECTION_DONE', () => {
+ const service = createService()
+ toHolidaySelectionForm(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+
+ expect(service.machine.current).toBe('addEmployeesHoliday')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions addEmployeesHoliday -> viewHolidayEmployees on HOLIDAY_ADD_EMPLOYEES_DONE', () => {
+ const service = createService()
+ toAddEmployeesHoliday(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
+
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('supports full happy path: policyList -> viewHolidayEmployees', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
+ expect(service.machine.current).toBe('holidaySelectionForm')
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+ expect(service.machine.current).toBe('addEmployeesHoliday')
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ })
+ })
+
+ describe('tab switching', () => {
+ it('switches from viewPolicyDetails to viewPolicyEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+ expect(service.machine.current).toBe('viewPolicyDetails')
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+
+ expect(service.machine.current).toBe('viewPolicyEmployees')
+ })
+
+ it('switches from viewPolicyEmployees to viewPolicyDetails', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+ expect(service.machine.current).toBe('viewPolicyEmployees')
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_DETAILS)
+
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ })
+
+ it('switches from viewHolidayEmployees to viewHolidaySchedule', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+
+ expect(service.machine.current).toBe('viewHolidaySchedule')
+ })
+
+ it('switches from viewHolidaySchedule to viewHolidayEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+ expect(service.machine.current).toBe('viewHolidaySchedule')
+
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES)
+
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ })
+ })
+
+ describe('back to list', () => {
+ it('returns to policyList from viewPolicyDetails', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('returns to policyList from viewPolicyEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('returns to policyList from viewHolidayEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('returns to policyList from viewHolidaySchedule', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+ })
+
+ describe('cancel transitions', () => {
+ it('cancels from policyTypeSelector to policyList', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('cancels from policyDetailsForm to policyList', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('cancels from policySettings to policyList', () => {
+ const service = createService()
+ toPolicySettings(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('cancels from addEmployeesToPolicy to policyList', () => {
+ const service = createService()
+ toAddEmployeesToPolicy(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('cancels from holidaySelectionForm to policyList', () => {
+ const service = createService()
+ toHolidaySelectionForm(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('cancels from addEmployeesHoliday to policyList', () => {
+ const service = createService()
+ toAddEmployeesHoliday(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+ })
+
+ describe('error transitions', () => {
+ it('policyDetailsForm error transitions to policyTypeSelector with alert', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Failed to create policy' },
+ })
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to create policy' }])
+ })
+
+ it('policyDetailsForm error without alert payload clears alerts', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {})
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('policySettings error transitions to policyDetailsForm with alert', () => {
+ const service = createService()
+ toPolicySettings(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
+ alert: { type: 'error', title: 'Failed to update settings' },
+ })
+
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.alerts).toEqual([
+ { type: 'error', title: 'Failed to update settings' },
+ ])
+ })
+
+ it('addEmployeesToPolicy error transitions to policySettings with alert', () => {
+ const service = createService()
+ toAddEmployeesToPolicy(service)
+
+ send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
+ alert: { type: 'error', title: 'Failed to add employees' },
+ })
+
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to add employees' }])
+ })
+
+ it('holidaySelectionForm error transitions to policyTypeSelector with alert', () => {
+ const service = createService()
+ toHolidaySelectionForm(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Failed to create holiday policy' },
+ })
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toEqual([
+ { type: 'error', title: 'Failed to create holiday policy' },
+ ])
+ })
+
+ it('addEmployeesHoliday error transitions to holidaySelectionForm with alert', () => {
+ const service = createService()
+ toAddEmployeesHoliday(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
+ alert: { type: 'error', title: 'Failed to add employees to holiday' },
+ })
+
+ expect(service.machine.current).toBe('holidaySelectionForm')
+ expect(service.context.alerts).toEqual([
+ { type: 'error', title: 'Failed to add employees to holiday' },
+ ])
+ })
+ })
+
+ describe('alert lifecycle', () => {
+ it('forward transition after error clears alerts', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Create failed' },
+ })
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toEqual([{ type: 'error', title: 'Create failed' }])
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('cancel clears alerts', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Create failed' },
+ })
+ expect(service.context.alerts).toBeDefined()
+
+ send(service, componentEvents.CANCEL)
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('create policy clears alerts from policyList', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('error then retry full flow preserves correct state', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Create failed' },
+ })
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toHaveLength(1)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.alerts).toBeUndefined()
+
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-456' })
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.policyId).toBe('policy-456')
+ expect(service.context.alerts).toBeUndefined()
+ })
+ })
+})
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
new file mode 100644
index 000000000..af33c841d
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
@@ -0,0 +1,359 @@
+import { transition, reduce, state, guard } from 'robot3'
+import {
+ PolicyListContextual,
+ PolicyTypeSelectorContextual,
+ PolicyDetailsFormContextual,
+ PolicySettingsContextual,
+ AddEmployeesToPolicyContextual,
+ ViewPolicyDetailsContextual,
+ ViewPolicyEmployeesContextual,
+ HolidaySelectionFormContextual,
+ AddEmployeesHolidayContextual,
+ ViewHolidayEmployeesContextual,
+ ViewHolidayScheduleContextual,
+ type TimeOffFlowContextInterface,
+ type TimeOffFlowAlert,
+} from './TimeOffFlowComponents'
+import { componentEvents } from '@/shared/constants'
+import type { MachineTransition } from '@/types/Helpers'
+
+type PolicyTypePayload = { policyType: 'sick' | 'vacation' | 'holiday' }
+type PolicyCreatedPayload = { policyId: string }
+type ErrorPayload = { alert?: TimeOffFlowAlert }
+type ViewPolicyPayload = { policyId: string; policyType: 'sick' | 'vacation' | 'holiday' }
+
+function isSickOrVacation(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
+ return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
+}
+
+function isHoliday(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
+ return ev.payload.policyType === 'holiday'
+}
+
+function isSickOrVacationView(
+ _ctx: TimeOffFlowContextInterface,
+ ev: { payload: ViewPolicyPayload },
+) {
+ return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
+}
+
+function isHolidayView(_ctx: TimeOffFlowContextInterface, ev: { payload: ViewPolicyPayload }) {
+ return ev.payload.policyType === 'holiday'
+}
+
+const cancelToPolicyList = transition(
+ componentEvents.CANCEL,
+ 'policyList',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyListContextual,
+ alerts: undefined,
+ }),
+ ),
+)
+
+const backToListTransition = transition(
+ componentEvents.TIME_OFF_BACK_TO_LIST,
+ 'policyList',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyListContextual,
+ alerts: undefined,
+ }),
+ ),
+)
+
+export const timeOffMachine = {
+ policyList: state(
+ transition(
+ componentEvents.TIME_OFF_CREATE_POLICY,
+ 'policyTypeSelector',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyTypeSelectorContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY,
+ 'viewPolicyDetails',
+ guard(isSickOrVacationView),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ViewPolicyPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyDetailsContextual,
+ policyId: ev.payload.policyId,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY,
+ 'viewHolidayEmployees',
+ guard(isHolidayView),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ViewPolicyPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayEmployeesContextual,
+ policyId: ev.payload.policyId,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ ),
+
+ policyTypeSelector: state(
+ transition(
+ componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
+ 'policyDetailsForm',
+ guard(isSickOrVacation),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: PolicyTypePayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyDetailsFormContextual,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
+ 'holidaySelectionForm',
+ guard(isHoliday),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: PolicyTypePayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: HolidaySelectionFormContextual,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ policyDetailsForm: state(
+ transition(
+ componentEvents.TIME_OFF_POLICY_DETAILS_DONE,
+ 'policySettings',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: PolicyCreatedPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicySettingsContextual,
+ policyId: ev.payload.policyId,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_POLICY_CREATE_ERROR,
+ 'policyTypeSelector',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyTypeSelectorContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ policySettings: state(
+ transition(
+ componentEvents.TIME_OFF_POLICY_SETTINGS_DONE,
+ 'addEmployeesToPolicy',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: AddEmployeesToPolicyContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR,
+ 'policyDetailsForm',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyDetailsFormContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ addEmployeesToPolicy: state(
+ transition(
+ componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE,
+ 'viewPolicyDetails',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyDetailsContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR,
+ 'policySettings',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicySettingsContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ viewPolicyDetails: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES,
+ 'viewPolicyEmployees',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyEmployeesContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ viewPolicyEmployees: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY_DETAILS,
+ 'viewPolicyDetails',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyDetailsContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ holidaySelectionForm: state(
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE,
+ 'addEmployeesHoliday',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: AddEmployeesHolidayContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR,
+ 'policyTypeSelector',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyTypeSelectorContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ addEmployeesHoliday: state(
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE,
+ 'viewHolidayEmployees',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayEmployeesContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR,
+ 'holidaySelectionForm',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: HolidaySelectionFormContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ viewHolidayEmployees: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE,
+ 'viewHolidaySchedule',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayScheduleContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ viewHolidaySchedule: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES,
+ 'viewHolidayEmployees',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayEmployeesContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ final: state(),
+}
diff --git a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
new file mode 100644
index 000000000..65d6fc30b
--- /dev/null
+++ b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewHolidayEmployeesProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function ViewHolidayEmployees(props: ViewHolidayEmployeesProps) {
+ return (
+
+
+
View Holiday Employees (companyId: {props.companyId})
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
new file mode 100644
index 000000000..6bf6eea02
--- /dev/null
+++ b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewHolidayScheduleProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function ViewHolidaySchedule(props: ViewHolidayScheduleProps) {
+ return (
+
+
+
View Holiday Schedule (companyId: {props.companyId})
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
new file mode 100644
index 000000000..4a7e3d43c
--- /dev/null
+++ b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewPolicyDetailsProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function ViewPolicyDetails(props: ViewPolicyDetailsProps) {
+ return (
+
+
+
View Policy Details (policyId: {props.policyId})
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
new file mode 100644
index 000000000..4c92bb122
--- /dev/null
+++ b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewPolicyEmployeesProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function ViewPolicyEmployees(props: ViewPolicyEmployeesProps) {
+ return (
+
+
+
View Policy Employees (policyId: {props.policyId})
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/index.ts b/src/components/TimeOff/index.ts
new file mode 100644
index 000000000..eac24ad97
--- /dev/null
+++ b/src/components/TimeOff/index.ts
@@ -0,0 +1,24 @@
+export { PolicyList } from './PolicyList/PolicyList'
+export type { PolicyListProps } from './PolicyList/PolicyList'
+export { PolicyTypeSelector } from './PolicyTypeSelector/PolicyTypeSelector'
+export type { PolicyTypeSelectorProps } from './PolicyTypeSelector/PolicyTypeSelector'
+export { PolicyDetailsForm } from './PolicyDetailsForm/PolicyDetailsForm'
+export type { PolicyDetailsFormProps } from './PolicyDetailsForm/PolicyDetailsForm'
+export { PolicySettings } from './PolicySettings/PolicySettings'
+export type { PolicySettingsProps } from './PolicySettings/PolicySettings'
+export { AddEmployeesToPolicy } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
+export type { AddEmployeesToPolicyProps } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
+export { ViewPolicyDetails } from './ViewPolicyDetails/ViewPolicyDetails'
+export type { ViewPolicyDetailsProps } from './ViewPolicyDetails/ViewPolicyDetails'
+export { ViewPolicyEmployees } from './ViewPolicyEmployees/ViewPolicyEmployees'
+export type { ViewPolicyEmployeesProps } from './ViewPolicyEmployees/ViewPolicyEmployees'
+export { HolidaySelectionForm } from './HolidaySelectionForm/HolidaySelectionForm'
+export type { HolidaySelectionFormProps } from './HolidaySelectionForm/HolidaySelectionForm'
+export { AddEmployeesHoliday } from './AddEmployeesHoliday/AddEmployeesHoliday'
+export type { AddEmployeesHolidayProps } from './AddEmployeesHoliday/AddEmployeesHoliday'
+export { ViewHolidayEmployees } from './ViewHolidayEmployees/ViewHolidayEmployees'
+export type { ViewHolidayEmployeesProps } from './ViewHolidayEmployees/ViewHolidayEmployees'
+export { ViewHolidaySchedule } from './ViewHolidaySchedule/ViewHolidaySchedule'
+export type { ViewHolidayScheduleProps } from './ViewHolidaySchedule/ViewHolidaySchedule'
+export { TimeOffFlow } from './TimeOffFlow/TimeOffFlow'
+export type { TimeOffFlowProps } from './TimeOffFlow/TimeOffFlowComponents'
From a529aef82c78fa2a9158f6de1fc0645f3ec2ee8d Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Mon, 30 Mar 2026 12:52:30 -0700
Subject: [PATCH 02/10] fix: formatting
---
.../AddEmployeesHoliday.tsx | 22 +++++++++----
.../AddEmployeesToPolicy.tsx | 22 +++++++++----
.../HolidaySelectionForm.tsx | 22 +++++++++----
.../PolicyDetailsForm/PolicyDetailsForm.tsx | 24 ++++++++------
.../TimeOff/PolicyList/PolicyList.tsx | 22 +++++++------
.../TimeOff/PolicySettings/PolicySettings.tsx | 22 +++++++++----
.../PolicyTypeSelector/PolicyTypeSelector.tsx | 32 +++++++++++--------
.../ViewHolidayEmployees.tsx | 12 +++++--
.../ViewHolidaySchedule.tsx | 12 +++++--
.../ViewPolicyDetails/ViewPolicyDetails.tsx | 12 +++++--
.../ViewPolicyEmployees.tsx | 12 +++++--
11 files changed, 151 insertions(+), 63 deletions(-)
diff --git a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
index fdda6176e..488d3e254 100644
--- a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
+++ b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
@@ -10,19 +10,29 @@ export function AddEmployeesHoliday(props: AddEmployeesHolidayProps) {
Add Employees to Holiday (companyId: {props.companyId})
-
)
diff --git a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
index d9b1f970a..2730ccfa9 100644
--- a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
+++ b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
@@ -10,19 +10,29 @@ export function AddEmployeesToPolicy(props: AddEmployeesToPolicyProps) {
Add Employees to Policy + Starting Balances (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
+ }}
+ >
Done
- { props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
alert: { type: 'error', title: 'Failed to add employees' },
- }); }
- }
+ })
+ }}
>
Simulate Error
- { props.onEvent(componentEvents.CANCEL); }}>Cancel
+ {
+ props.onEvent(componentEvents.CANCEL)
+ }}
+ >
+ Cancel
+
)
diff --git a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
index 00603a872..8b8fb0d4b 100644
--- a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
+++ b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
@@ -10,19 +10,29 @@ export function HolidaySelectionForm(props: HolidaySelectionFormProps) {
Holiday Selection Form (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+ }}
+ >
Done
- { props.onEvent(componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
alert: { type: 'error', title: 'Failed to create holiday policy' },
- }); }
- }
+ })
+ }}
>
Simulate Error
- { props.onEvent(componentEvents.CANCEL); }}>Cancel
+ {
+ props.onEvent(componentEvents.CANCEL)
+ }}
+ >
+ Cancel
+
)
diff --git a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
index 0e796cdf6..3d5d6e104 100644
--- a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
+++ b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
@@ -14,24 +14,30 @@ export function PolicyDetailsForm(props: PolicyDetailsFormProps) {
Policy Details Form (type: {props.policyType}, companyId: {props.companyId})
- { props.onEvent(componentEvents.TIME_OFF_POLICY_DETAILS_DONE, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_DETAILS_DONE, {
policyId: 'mock-policy-id',
- }); }
- }
+ })
+ }}
>
Done
- { props.onEvent(componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
alert: { type: 'error', title: 'Failed to create policy' },
- }); }
- }
+ })
+ }}
>
Simulate Error
- { props.onEvent(componentEvents.CANCEL); }}>Cancel
+ {
+ props.onEvent(componentEvents.CANCEL)
+ }}
+ >
+ Cancel
+
)
diff --git a/src/components/TimeOff/PolicyList/PolicyList.tsx b/src/components/TimeOff/PolicyList/PolicyList.tsx
index 8e2f5afd5..ce468323e 100644
--- a/src/components/TimeOff/PolicyList/PolicyList.tsx
+++ b/src/components/TimeOff/PolicyList/PolicyList.tsx
@@ -10,26 +10,30 @@ export function PolicyList(props: PolicyListProps) {
Policy List (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_CREATE_POLICY); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_CREATE_POLICY)
+ }}
+ >
Create Policy
- { props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
policyId: 'mock-policy-id',
policyType: 'vacation',
- }); }
- }
+ })
+ }}
>
View Vacation Policy
- { props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
policyId: 'mock-policy-id',
policyType: 'holiday',
- }); }
- }
+ })
+ }}
>
View Holiday Policy
diff --git a/src/components/TimeOff/PolicySettings/PolicySettings.tsx b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
index c05d05d8e..77c14de1f 100644
--- a/src/components/TimeOff/PolicySettings/PolicySettings.tsx
+++ b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
@@ -10,19 +10,29 @@ export function PolicySettings(props: PolicySettingsProps) {
Policy Settings (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_DONE); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+ }}
+ >
Done
- { props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
alert: { type: 'error', title: 'Failed to update policy settings' },
- }); }
- }
+ })
+ }}
>
Simulate Error
- { props.onEvent(componentEvents.CANCEL); }}>Cancel
+ {
+ props.onEvent(componentEvents.CANCEL)
+ }}
+ >
+ Cancel
+
)
diff --git a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
index 792fa8c55..48c14aa6a 100644
--- a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
+++ b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
@@ -11,33 +11,39 @@ export function PolicyTypeSelector(props: PolicyTypeSelectorProps) {
Policy Type Selector (companyId: {props.companyId})
- { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
policyType: 'sick',
- }); }
- }
+ })
+ }}
>
Sick
- { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
policyType: 'vacation',
- }); }
- }
+ })
+ }}
>
Vacation
- { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
+ onClick={() => {
+ props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
policyType: 'holiday',
- }); }
- }
+ })
+ }}
>
Company Holiday
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
{
+ props.onEvent(componentEvents.CANCEL)
+ }}
+ >
+ Cancel
+
)
diff --git a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
index 65d6fc30b..d73cea0cd 100644
--- a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
+++ b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
@@ -10,10 +10,18 @@ export function ViewHolidayEmployees(props: ViewHolidayEmployeesProps) {
View Holiday Employees (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+ }}
+ >
Holiday Schedule Tab
- { props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
+ }}
+ >
Back to List
diff --git a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
index 6bf6eea02..23a9786fb 100644
--- a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
+++ b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
@@ -10,10 +10,18 @@ export function ViewHolidaySchedule(props: ViewHolidayScheduleProps) {
View Holiday Schedule (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES)
+ }}
+ >
View Employees Tab
- { props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
+ }}
+ >
Back to List
diff --git a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
index 4a7e3d43c..c031a37de 100644
--- a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
+++ b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
@@ -10,10 +10,18 @@ export function ViewPolicyDetails(props: ViewPolicyDetailsProps) {
View Policy Details (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+ }}
+ >
View Employees Tab
- { props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
+ }}
+ >
Back to List
diff --git a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
index 4c92bb122..d9bc6306c 100644
--- a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
+++ b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
@@ -10,10 +10,18 @@ export function ViewPolicyEmployees(props: ViewPolicyEmployeesProps) {
View Policy Employees (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_DETAILS); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_DETAILS)
+ }}
+ >
Policy Details Tab
- { props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ {
+ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
+ }}
+ >
Back to List
From 36a5ae60febc37a4e251575aa108978d9f0430c6 Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Mon, 30 Mar 2026 13:29:01 -0700
Subject: [PATCH 03/10] feat(SDK-551): add PolicyListPresentation component
Stateless presentation component for the time off policy list. Renders
a DataView table with Name/Enrolled columns, per-row overflow menu
(Edit/Delete), Finish setup button for incomplete policies, delete
confirmation dialog, dismissible success alert, and empty state.
Also updates translations to match Figma designs.
Made-with: Cursor
---
.../PolicyListPresentation.module.scss | 6 +
.../PolicyList/PolicyListPresentation.tsx | 144 ++++++++++++++++++
.../TimeOff/PolicyList/PolicyListTypes.ts | 18 +++
.../en/Company.TimeOff.TimeOffPolicies.json | 20 ++-
src/types/i18next.d.ts | 10 +-
5 files changed, 187 insertions(+), 11 deletions(-)
create mode 100644 src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss
create mode 100644 src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
create mode 100644 src/components/TimeOff/PolicyList/PolicyListTypes.ts
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss b/src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss
new file mode 100644
index 000000000..b33b09745
--- /dev/null
+++ b/src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss
@@ -0,0 +1,6 @@
+.actionsCell {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: toRem(12);
+}
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx b/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
new file mode 100644
index 000000000..9deb2a9a5
--- /dev/null
+++ b/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
@@ -0,0 +1,144 @@
+import { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import type { PolicyListPresentationProps, PolicyListItem } from './PolicyListTypes'
+import styles from './PolicyListPresentation.module.scss'
+import {
+ DataView,
+ Flex,
+ EmptyData,
+ ActionsLayout,
+ HamburgerMenu,
+ useDataView,
+} from '@/components/Common'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+import { useI18n } from '@/i18n'
+
+export function PolicyListPresentation({
+ policies,
+ onCreatePolicy,
+ onEditPolicy,
+ onFinishSetup,
+ onDeletePolicy,
+ deleteSuccessAlert,
+ onDismissDeleteAlert,
+ isDeletingPolicyId,
+}: PolicyListPresentationProps) {
+ const { Button, Heading, Text, Alert, Dialog } = useComponentContext()
+ useI18n('Company.TimeOff.TimeOffPolicies')
+ const { t } = useTranslation('Company.TimeOff.TimeOffPolicies')
+
+ const [deletePolicyDialogState, setDeletePolicyDialogState] = useState<{
+ isOpen: boolean
+ policy: PolicyListItem | null
+ }>({
+ isOpen: false,
+ policy: null,
+ })
+
+ const handleOpenDeleteDialog = (policy: PolicyListItem) => {
+ setDeletePolicyDialogState({ isOpen: true, policy })
+ }
+
+ const handleCloseDeleteDialog = () => {
+ setDeletePolicyDialogState({ isOpen: false, policy: null })
+ }
+
+ const handleConfirmDelete = () => {
+ if (deletePolicyDialogState.policy) {
+ onDeletePolicy(deletePolicyDialogState.policy)
+ handleCloseDeleteDialog()
+ }
+ }
+
+ const { ...dataViewProps } = useDataView({
+ data: policies,
+ columns: [
+ {
+ title: t('tableHeaders.name'),
+ render: (policy: PolicyListItem) => {policy.name},
+ },
+ {
+ title: t('tableHeaders.enrolled'),
+ render: (policy: PolicyListItem) => (
+ {policy.enrolledDisplay}
+ ),
+ },
+ ],
+ itemMenu: (policy: PolicyListItem) => {
+ const isDeleting = isDeletingPolicyId === policy.uuid
+
+ return (
+
+ {!policy.isComplete && (
+ { onFinishSetup(policy); }}>
+ {t('finishSetupCta')}
+
+ )}
+ { onEditPolicy(policy); },
+ },
+ {
+ label: t('actions.deletePolicy'),
+ onClick: () => { handleOpenDeleteDialog(policy); },
+ },
+ ]}
+ />
+
+ )
+ },
+ emptyState: () => (
+
+
+
+ {t('createPolicyCta')}
+
+
+
+ ),
+ })
+
+ return (
+
+ {deleteSuccessAlert && (
+
+ )}
+
+
+ {t('pageTitle')}
+ {policies.length > 0 && (
+
+ {t('createPolicyCta')}
+
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyList/PolicyListTypes.ts b/src/components/TimeOff/PolicyList/PolicyListTypes.ts
new file mode 100644
index 000000000..775ca1ebc
--- /dev/null
+++ b/src/components/TimeOff/PolicyList/PolicyListTypes.ts
@@ -0,0 +1,18 @@
+export interface PolicyListItem {
+ uuid: string
+ name: string
+ policyType: string
+ isComplete: boolean
+ enrolledDisplay: string
+}
+
+export interface PolicyListPresentationProps {
+ policies: PolicyListItem[]
+ onCreatePolicy: () => void
+ onEditPolicy: (policy: PolicyListItem) => void
+ onFinishSetup: (policy: PolicyListItem) => void
+ onDeletePolicy: (policy: PolicyListItem) => void
+ deleteSuccessAlert?: string | null
+ onDismissDeleteAlert?: () => void
+ isDeletingPolicyId?: string | null
+}
diff --git a/src/i18n/en/Company.TimeOff.TimeOffPolicies.json b/src/i18n/en/Company.TimeOff.TimeOffPolicies.json
index 19f393b76..7c7be940c 100644
--- a/src/i18n/en/Company.TimeOff.TimeOffPolicies.json
+++ b/src/i18n/en/Company.TimeOff.TimeOffPolicies.json
@@ -1,31 +1,35 @@
{
- "pageTitle": "Time off policies",
- "addPolicyCta": "Add policy",
+ "pageTitle": "Time Off Policies",
+ "createPolicyCta": "Create policy",
"tableHeaders": {
"name": "Name",
- "enrolled": "Enrolled",
- "actions": "Actions"
+ "enrolled": "Enrolled"
},
+ "tableLabel": "Time off policies",
"actions": {
- "viewPolicy": "View policy",
"editPolicy": "Edit policy",
"deletePolicy": "Delete policy"
},
+ "finishSetupCta": "Finish setup",
+ "allEmployeesLabel": "All employees",
+ "enrolledDash": "\u2013",
"employeeCount_one": "{{count}} employee",
"employeeCount_other": "{{count}} employees",
"incompleteBadge": "Incomplete",
"holidayPayPolicy": "Holiday pay policy",
"deletePolicyDialog": {
"title": "Are you sure you want to delete the policy \"{{name}}\"?",
- "description": "This will delete the policy \"{{name}}\" and all associated time off requests."
+ "description": "This will delete the policy \"{{name}}\" and all associated time off requests.",
+ "confirmCta": "Delete policy",
+ "cancelCta": "Cancel"
},
"deleteHolidayDialog": {
"title": "Are you sure you want to delete the company holiday pay policy?",
"description": "This will delete the company holiday pay policy."
},
"emptyState": {
- "heading": "You haven't added any time off policies yet",
- "body": "Time off policies help you track and approve employee leave."
+ "heading": "You don't have any time off policies",
+ "body": "Manage employee time off by creating a policy."
},
"selectPolicyType": {
"title": "Select policy type",
diff --git a/src/types/i18next.d.ts b/src/types/i18next.d.ts
index 42145bead..5ccfd1669 100644
--- a/src/types/i18next.d.ts
+++ b/src/types/i18next.d.ts
@@ -479,17 +479,19 @@ export interface CompanyTimeOffHolidayPolicy{
};
export interface CompanyTimeOffTimeOffPolicies{
"pageTitle":string;
-"addPolicyCta":string;
+"createPolicyCta":string;
"tableHeaders":{
"name":string;
"enrolled":string;
-"actions":string;
};
+"tableLabel":string;
"actions":{
-"viewPolicy":string;
"editPolicy":string;
"deletePolicy":string;
};
+"finishSetupCta":string;
+"allEmployeesLabel":string;
+"enrolledDash":string;
"employeeCount_one":string;
"employeeCount_other":string;
"incompleteBadge":string;
@@ -497,6 +499,8 @@ export interface CompanyTimeOffTimeOffPolicies{
"deletePolicyDialog":{
"title":string;
"description":string;
+"confirmCta":string;
+"cancelCta":string;
};
"deleteHolidayDialog":{
"title":string;
From 2152e5a8d1dbad63e1cd6a559285d7ff7f4c820b Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Mon, 30 Mar 2026 13:44:19 -0700
Subject: [PATCH 04/10] style: fix prettier formatting in
PolicyListPresentation
Made-with: Cursor
---
.../TimeOff/PolicyList/PolicyListPresentation.tsx | 15 ++++++++++++---
1 file changed, 12 insertions(+), 3 deletions(-)
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx b/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
index 9deb2a9a5..937ce5100 100644
--- a/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
+++ b/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
@@ -70,7 +70,12 @@ export function PolicyListPresentation({
return (
{!policy.isComplete && (
-
{ onFinishSetup(policy); }}>
+ {
+ onFinishSetup(policy)
+ }}
+ >
{t('finishSetupCta')}
)}
@@ -80,11 +85,15 @@ export function PolicyListPresentation({
items={[
{
label: t('actions.editPolicy'),
- onClick: () => { onEditPolicy(policy); },
+ onClick: () => {
+ onEditPolicy(policy)
+ },
},
{
label: t('actions.deletePolicy'),
- onClick: () => { handleOpenDeleteDialog(policy); },
+ onClick: () => {
+ handleOpenDeleteDialog(policy)
+ },
},
]}
/>
From 2f37c353d17bf119fad16d14a299c10e9f2f3eeb Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Thu, 2 Apr 2026 09:39:23 -0700
Subject: [PATCH 05/10] fix: e2e tests
---
e2e/main.tsx | 59 +-
.../AddEmployeesHoliday.tsx | 39 --
.../AddEmployeesToPolicy.tsx | 39 --
.../HolidaySelectionForm.tsx | 39 --
.../PolicyDetailsForm/PolicyDetailsForm.tsx | 44 --
.../TimeOff/PolicyList/PolicyList.tsx | 43 --
.../PolicyListPresentation.module.scss | 6 -
.../PolicyList/PolicyListPresentation.tsx | 153 -----
.../TimeOff/PolicyList/PolicyListTypes.ts | 18 -
.../TimeOff/PolicySettings/PolicySettings.tsx | 39 --
.../PolicyTypeSelector/PolicyTypeSelector.tsx | 50 --
.../TimeOff/TimeOffFlow/TimeOffFlow.tsx | 23 -
.../TimeOffFlow/TimeOffFlowComponents.tsx | 150 -----
src/components/TimeOff/TimeOffFlow/index.ts | 2 -
.../TimeOffFlow/timeOffStateMachine.test.ts | 562 ------------------
.../TimeOffFlow/timeOffStateMachine.ts | 359 -----------
.../ViewHolidayEmployees.tsx | 30 -
.../ViewHolidaySchedule.tsx | 30 -
.../ViewPolicyDetails/ViewPolicyDetails.tsx | 30 -
.../ViewPolicyEmployees.tsx | 30 -
src/components/TimeOff/index.ts | 24 -
21 files changed, 34 insertions(+), 1735 deletions(-)
delete mode 100644 src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
delete mode 100644 src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
delete mode 100644 src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
delete mode 100644 src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
delete mode 100644 src/components/TimeOff/PolicyList/PolicyList.tsx
delete mode 100644 src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss
delete mode 100644 src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
delete mode 100644 src/components/TimeOff/PolicyList/PolicyListTypes.ts
delete mode 100644 src/components/TimeOff/PolicySettings/PolicySettings.tsx
delete mode 100644 src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
delete mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
delete mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
delete mode 100644 src/components/TimeOff/TimeOffFlow/index.ts
delete mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
delete mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
delete mode 100644 src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
delete mode 100644 src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
delete mode 100644 src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
delete mode 100644 src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
delete mode 100644 src/components/TimeOff/index.ts
diff --git a/e2e/main.tsx b/e2e/main.tsx
index e340c5194..a006c57f4 100644
--- a/e2e/main.tsx
+++ b/e2e/main.tsx
@@ -114,33 +114,42 @@ const FLOW_OPTIONS: { value: FlowType; label: string }[] = [
]
function FlowSelector({ currentFlow }: { currentFlow: FlowType }) {
- const handleChange = (e: React.ChangeEvent) => {
- const params = new URLSearchParams(window.location.search)
- params.set('flow', e.target.value)
- window.location.search = params.toString()
- }
-
return (
-
-
-
-
+ Flow:
+ {FLOW_OPTIONS.map((opt, i) => {
+ const params = new URLSearchParams(window.location.search)
+ params.set('flow', opt.value)
+ const isActive = opt.value === currentFlow
+ return (
+
+ {i > 0 && |}
+
+ {opt.label}
+
+
+ )
+ })}
+
)
}
diff --git a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
deleted file mode 100644
index 488d3e254..000000000
--- a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface AddEmployeesHolidayProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function AddEmployeesHoliday(props: AddEmployeesHolidayProps) {
- return (
-
-
-
Add Employees to Holiday (companyId: {props.companyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
- }}
- >
- Done
-
-
{
- props.onEvent(componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees to holiday' },
- })
- }}
- >
- Simulate Error
-
-
{
- props.onEvent(componentEvents.CANCEL)
- }}
- >
- Cancel
-
-
-
- )
-}
diff --git a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
deleted file mode 100644
index 2730ccfa9..000000000
--- a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface AddEmployeesToPolicyProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function AddEmployeesToPolicy(props: AddEmployeesToPolicyProps) {
- return (
-
-
-
Add Employees to Policy + Starting Balances (policyId: {props.policyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
- }}
- >
- Done
-
-
{
- props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees' },
- })
- }}
- >
- Simulate Error
-
-
{
- props.onEvent(componentEvents.CANCEL)
- }}
- >
- Cancel
-
-
-
- )
-}
diff --git a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
deleted file mode 100644
index 8b8fb0d4b..000000000
--- a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface HolidaySelectionFormProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function HolidaySelectionForm(props: HolidaySelectionFormProps) {
- return (
-
-
-
Holiday Selection Form (companyId: {props.companyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
- }}
- >
- Done
-
-
{
- props.onEvent(componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create holiday policy' },
- })
- }}
- >
- Simulate Error
-
-
{
- props.onEvent(componentEvents.CANCEL)
- }}
- >
- Cancel
-
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
deleted file mode 100644
index 3d5d6e104..000000000
--- a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicyDetailsFormProps extends BaseComponentInterface {
- companyId: string
- policyType: 'sick' | 'vacation'
-}
-
-export function PolicyDetailsForm(props: PolicyDetailsFormProps) {
- return (
-
-
-
- Policy Details Form (type: {props.policyType}, companyId: {props.companyId})
-
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_DETAILS_DONE, {
- policyId: 'mock-policy-id',
- })
- }}
- >
- Done
-
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create policy' },
- })
- }}
- >
- Simulate Error
-
-
{
- props.onEvent(componentEvents.CANCEL)
- }}
- >
- Cancel
-
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyList/PolicyList.tsx b/src/components/TimeOff/PolicyList/PolicyList.tsx
deleted file mode 100644
index ce468323e..000000000
--- a/src/components/TimeOff/PolicyList/PolicyList.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicyListProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function PolicyList(props: PolicyListProps) {
- return (
-
-
-
Policy List (companyId: {props.companyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_CREATE_POLICY)
- }}
- >
- Create Policy
-
-
{
- props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'mock-policy-id',
- policyType: 'vacation',
- })
- }}
- >
- View Vacation Policy
-
-
{
- props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'mock-policy-id',
- policyType: 'holiday',
- })
- }}
- >
- View Holiday Policy
-
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss b/src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss
deleted file mode 100644
index b33b09745..000000000
--- a/src/components/TimeOff/PolicyList/PolicyListPresentation.module.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-.actionsCell {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- gap: toRem(12);
-}
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx b/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
deleted file mode 100644
index 937ce5100..000000000
--- a/src/components/TimeOff/PolicyList/PolicyListPresentation.tsx
+++ /dev/null
@@ -1,153 +0,0 @@
-import { useState } from 'react'
-import { useTranslation } from 'react-i18next'
-import type { PolicyListPresentationProps, PolicyListItem } from './PolicyListTypes'
-import styles from './PolicyListPresentation.module.scss'
-import {
- DataView,
- Flex,
- EmptyData,
- ActionsLayout,
- HamburgerMenu,
- useDataView,
-} from '@/components/Common'
-import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
-import { useI18n } from '@/i18n'
-
-export function PolicyListPresentation({
- policies,
- onCreatePolicy,
- onEditPolicy,
- onFinishSetup,
- onDeletePolicy,
- deleteSuccessAlert,
- onDismissDeleteAlert,
- isDeletingPolicyId,
-}: PolicyListPresentationProps) {
- const { Button, Heading, Text, Alert, Dialog } = useComponentContext()
- useI18n('Company.TimeOff.TimeOffPolicies')
- const { t } = useTranslation('Company.TimeOff.TimeOffPolicies')
-
- const [deletePolicyDialogState, setDeletePolicyDialogState] = useState<{
- isOpen: boolean
- policy: PolicyListItem | null
- }>({
- isOpen: false,
- policy: null,
- })
-
- const handleOpenDeleteDialog = (policy: PolicyListItem) => {
- setDeletePolicyDialogState({ isOpen: true, policy })
- }
-
- const handleCloseDeleteDialog = () => {
- setDeletePolicyDialogState({ isOpen: false, policy: null })
- }
-
- const handleConfirmDelete = () => {
- if (deletePolicyDialogState.policy) {
- onDeletePolicy(deletePolicyDialogState.policy)
- handleCloseDeleteDialog()
- }
- }
-
- const { ...dataViewProps } = useDataView({
- data: policies,
- columns: [
- {
- title: t('tableHeaders.name'),
- render: (policy: PolicyListItem) => {policy.name},
- },
- {
- title: t('tableHeaders.enrolled'),
- render: (policy: PolicyListItem) => (
- {policy.enrolledDisplay}
- ),
- },
- ],
- itemMenu: (policy: PolicyListItem) => {
- const isDeleting = isDeletingPolicyId === policy.uuid
-
- return (
-
- {!policy.isComplete && (
- {
- onFinishSetup(policy)
- }}
- >
- {t('finishSetupCta')}
-
- )}
- {
- onEditPolicy(policy)
- },
- },
- {
- label: t('actions.deletePolicy'),
- onClick: () => {
- handleOpenDeleteDialog(policy)
- },
- },
- ]}
- />
-
- )
- },
- emptyState: () => (
-
-
-
- {t('createPolicyCta')}
-
-
-
- ),
- })
-
- return (
-
- {deleteSuccessAlert && (
-
- )}
-
-
- {t('pageTitle')}
- {policies.length > 0 && (
-
- {t('createPolicyCta')}
-
- )}
-
-
-
-
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyList/PolicyListTypes.ts b/src/components/TimeOff/PolicyList/PolicyListTypes.ts
deleted file mode 100644
index 775ca1ebc..000000000
--- a/src/components/TimeOff/PolicyList/PolicyListTypes.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export interface PolicyListItem {
- uuid: string
- name: string
- policyType: string
- isComplete: boolean
- enrolledDisplay: string
-}
-
-export interface PolicyListPresentationProps {
- policies: PolicyListItem[]
- onCreatePolicy: () => void
- onEditPolicy: (policy: PolicyListItem) => void
- onFinishSetup: (policy: PolicyListItem) => void
- onDeletePolicy: (policy: PolicyListItem) => void
- deleteSuccessAlert?: string | null
- onDismissDeleteAlert?: () => void
- isDeletingPolicyId?: string | null
-}
diff --git a/src/components/TimeOff/PolicySettings/PolicySettings.tsx b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
deleted file mode 100644
index 77c14de1f..000000000
--- a/src/components/TimeOff/PolicySettings/PolicySettings.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicySettingsProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function PolicySettings(props: PolicySettingsProps) {
- return (
-
-
-
Policy Settings (policyId: {props.policyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
- }}
- >
- Done
-
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
- alert: { type: 'error', title: 'Failed to update policy settings' },
- })
- }}
- >
- Simulate Error
-
-
{
- props.onEvent(componentEvents.CANCEL)
- }}
- >
- Cancel
-
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
deleted file mode 100644
index 48c14aa6a..000000000
--- a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicyTypeSelectorProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function PolicyTypeSelector(props: PolicyTypeSelectorProps) {
- return (
-
-
-
Policy Type Selector (companyId: {props.companyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
- policyType: 'sick',
- })
- }}
- >
- Sick
-
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
- policyType: 'vacation',
- })
- }}
- >
- Vacation
-
-
{
- props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
- policyType: 'holiday',
- })
- }}
- >
- Company Holiday
-
-
{
- props.onEvent(componentEvents.CANCEL)
- }}
- >
- Cancel
-
-
-
- )
-}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
deleted file mode 100644
index 5b22ec216..000000000
--- a/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { createMachine } from 'robot3'
-import { useMemo } from 'react'
-import { timeOffMachine } from './timeOffStateMachine'
-import type { TimeOffFlowProps, TimeOffFlowContextInterface } from './TimeOffFlowComponents'
-import { PolicyListContextual } from './TimeOffFlowComponents'
-import { Flow } from '@/components/Flow/Flow'
-
-export const TimeOffFlow = ({ companyId, onEvent }: TimeOffFlowProps) => {
- const timeOffFlow = useMemo(
- () =>
- createMachine(
- 'policyList',
- timeOffMachine,
- (initialContext: TimeOffFlowContextInterface) => ({
- ...initialContext,
- component: PolicyListContextual,
- companyId,
- }),
- ),
- [companyId],
- )
- return
-}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
deleted file mode 100644
index b9c6ca237..000000000
--- a/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import type { ReactNode } from 'react'
-import { PolicyList } from '../PolicyList/PolicyList'
-import { PolicyTypeSelector } from '../PolicyTypeSelector/PolicyTypeSelector'
-import { PolicyDetailsForm } from '../PolicyDetailsForm/PolicyDetailsForm'
-import { PolicySettings } from '../PolicySettings/PolicySettings'
-import { AddEmployeesToPolicy } from '../AddEmployeesToPolicy/AddEmployeesToPolicy'
-import { ViewPolicyDetails } from '../ViewPolicyDetails/ViewPolicyDetails'
-import { ViewPolicyEmployees } from '../ViewPolicyEmployees/ViewPolicyEmployees'
-import { HolidaySelectionForm } from '../HolidaySelectionForm/HolidaySelectionForm'
-import { AddEmployeesHoliday } from '../AddEmployeesHoliday/AddEmployeesHoliday'
-import { ViewHolidayEmployees } from '../ViewHolidayEmployees/ViewHolidayEmployees'
-import { ViewHolidaySchedule } from '../ViewHolidaySchedule/ViewHolidaySchedule'
-import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow'
-import type { BaseComponentInterface } from '@/components/Base'
-import { Flex } from '@/components/Common'
-import { ensureRequired } from '@/helpers/ensureRequired'
-import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
-
-export interface TimeOffFlowProps extends BaseComponentInterface {
- companyId: string
-}
-
-export type TimeOffFlowAlert = {
- type: 'error' | 'info' | 'success'
- title: string
- content?: ReactNode
-}
-
-export type TimeOffPolicyType = 'sick' | 'vacation' | 'holiday'
-
-export interface TimeOffFlowContextInterface extends FlowContextInterface {
- companyId: string
- policyType?: TimeOffPolicyType
- policyId?: string
- alerts?: TimeOffFlowAlert[]
-}
-
-export function PolicyListContextual() {
- const { onEvent, companyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function PolicyTypeSelectorContextual() {
- const { onEvent, companyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function PolicyDetailsFormContextual() {
- const { onEvent, companyId, policyType, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function PolicySettingsContextual() {
- const { onEvent, policyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function AddEmployeesToPolicyContextual() {
- const { onEvent, policyId } = useFlow()
- return
-}
-
-export function ViewPolicyDetailsContextual() {
- const { onEvent, policyId } = useFlow()
- return
-}
-
-export function ViewPolicyEmployeesContextual() {
- const { onEvent, policyId } = useFlow()
- return
-}
-
-export function HolidaySelectionFormContextual() {
- const { onEvent, companyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function AddEmployeesHolidayContextual() {
- const { onEvent, companyId } = useFlow()
- return
-}
-
-export function ViewHolidayEmployeesContextual() {
- const { onEvent, companyId } = useFlow()
- return
-}
-
-export function ViewHolidayScheduleContextual() {
- const { onEvent, companyId } = useFlow()
- return
-}
diff --git a/src/components/TimeOff/TimeOffFlow/index.ts b/src/components/TimeOff/TimeOffFlow/index.ts
deleted file mode 100644
index db1e7c9b2..000000000
--- a/src/components/TimeOff/TimeOffFlow/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { TimeOffFlow } from './TimeOffFlow'
-export type { TimeOffFlowProps } from './TimeOffFlowComponents'
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
deleted file mode 100644
index a4268c98c..000000000
--- a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
+++ /dev/null
@@ -1,562 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { createMachine, interpret, type SendFunction } from 'robot3'
-import { timeOffMachine } from './timeOffStateMachine'
-import type { TimeOffFlowContextInterface } from './TimeOffFlowComponents'
-import { componentEvents } from '@/shared/constants'
-
-type TimeOffState =
- | 'policyList'
- | 'policyTypeSelector'
- | 'policyDetailsForm'
- | 'policySettings'
- | 'addEmployeesToPolicy'
- | 'viewPolicyDetails'
- | 'viewPolicyEmployees'
- | 'holidaySelectionForm'
- | 'addEmployeesHoliday'
- | 'viewHolidayEmployees'
- | 'viewHolidaySchedule'
- | 'final'
-
-function createTestMachine(initialState: TimeOffState = 'policyList') {
- return createMachine(
- initialState,
- timeOffMachine,
- (initialContext: TimeOffFlowContextInterface) => ({
- ...initialContext,
- component: () => null,
- companyId: 'company-123',
- }),
- )
-}
-
-function createService(initialState: TimeOffState = 'policyList') {
- const machine = createTestMachine(initialState)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return interpret(machine, () => {}, {} as any)
-}
-
-function send(service: ReturnType, type: string, payload?: unknown) {
- ;(service.send as SendFunction)({ type, payload })
-}
-
-function toPolicyTypeSelector(service: ReturnType) {
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- expect(service.machine.current).toBe('policyTypeSelector')
-}
-
-function toPolicyDetailsForm(service: ReturnType) {
- toPolicyTypeSelector(service)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
- expect(service.machine.current).toBe('policyDetailsForm')
-}
-
-function toPolicySettings(service: ReturnType) {
- toPolicyDetailsForm(service)
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
- expect(service.machine.current).toBe('policySettings')
-}
-
-function toAddEmployeesToPolicy(service: ReturnType) {
- toPolicySettings(service)
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
- expect(service.machine.current).toBe('addEmployeesToPolicy')
-}
-
-function toHolidaySelectionForm(service: ReturnType) {
- toPolicyTypeSelector(service)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
- expect(service.machine.current).toBe('holidaySelectionForm')
-}
-
-function toAddEmployeesHoliday(service: ReturnType) {
- toHolidaySelectionForm(service)
- send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
- expect(service.machine.current).toBe('addEmployeesHoliday')
-}
-
-describe('timeOffStateMachine', () => {
- describe('policyList state', () => {
- it('starts in policyList by default', () => {
- const service = createService()
- expect(service.machine.current).toBe('policyList')
- })
-
- it('transitions to policyTypeSelector on TIME_OFF_CREATE_POLICY', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to viewPolicyDetails on TIME_OFF_VIEW_POLICY with sick/vacation type', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-456',
- policyType: 'vacation',
- })
-
- expect(service.machine.current).toBe('viewPolicyDetails')
- expect(service.context.policyId).toBe('policy-456')
- expect(service.context.policyType).toBe('vacation')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to viewHolidayEmployees on TIME_OFF_VIEW_POLICY with holiday type', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-789',
- policyType: 'holiday',
- })
-
- expect(service.machine.current).toBe('viewHolidayEmployees')
- expect(service.context.policyId).toBe('policy-789')
- expect(service.context.policyType).toBe('holiday')
- expect(service.context.alerts).toBeUndefined()
- })
- })
-
- describe('policyTypeSelector state', () => {
- it('transitions to policyDetailsForm on sick type selection', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
-
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.policyType).toBe('sick')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to policyDetailsForm on vacation type selection', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
-
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.policyType).toBe('vacation')
- })
-
- it('transitions to holidaySelectionForm on holiday type selection', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
-
- expect(service.machine.current).toBe('holidaySelectionForm')
- expect(service.context.policyType).toBe('holiday')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to policyList on CANCEL', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
- })
-
- describe('sick/vacation creation flow', () => {
- it('transitions policyDetailsForm -> policySettings on POLICY_DETAILS_DONE', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
-
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.policyId).toBe('policy-123')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions policySettings -> addEmployeesToPolicy on POLICY_SETTINGS_DONE', () => {
- const service = createService()
- toPolicySettings(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
-
- expect(service.machine.current).toBe('addEmployeesToPolicy')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions addEmployeesToPolicy -> viewPolicyDetails on ADD_EMPLOYEES_DONE', () => {
- const service = createService()
- toAddEmployeesToPolicy(service)
-
- send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
-
- expect(service.machine.current).toBe('viewPolicyDetails')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('supports full happy path: policyList -> viewPolicyDetails', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- expect(service.machine.current).toBe('policyTypeSelector')
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.policyType).toBe('vacation')
-
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.policyId).toBe('policy-123')
-
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
- expect(service.machine.current).toBe('addEmployeesToPolicy')
-
- send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
- expect(service.machine.current).toBe('viewPolicyDetails')
- expect(service.context.policyId).toBe('policy-123')
- })
- })
-
- describe('holiday creation flow', () => {
- it('transitions holidaySelectionForm -> addEmployeesHoliday on HOLIDAY_SELECTION_DONE', () => {
- const service = createService()
- toHolidaySelectionForm(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
-
- expect(service.machine.current).toBe('addEmployeesHoliday')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions addEmployeesHoliday -> viewHolidayEmployees on HOLIDAY_ADD_EMPLOYEES_DONE', () => {
- const service = createService()
- toAddEmployeesHoliday(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
-
- expect(service.machine.current).toBe('viewHolidayEmployees')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('supports full happy path: policyList -> viewHolidayEmployees', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
- expect(service.machine.current).toBe('holidaySelectionForm')
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
- expect(service.machine.current).toBe('addEmployeesHoliday')
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
- expect(service.machine.current).toBe('viewHolidayEmployees')
- })
- })
-
- describe('tab switching', () => {
- it('switches from viewPolicyDetails to viewPolicyEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
- expect(service.machine.current).toBe('viewPolicyDetails')
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
-
- expect(service.machine.current).toBe('viewPolicyEmployees')
- })
-
- it('switches from viewPolicyEmployees to viewPolicyDetails', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
- expect(service.machine.current).toBe('viewPolicyEmployees')
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_DETAILS)
-
- expect(service.machine.current).toBe('viewPolicyDetails')
- })
-
- it('switches from viewHolidayEmployees to viewHolidaySchedule', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
- expect(service.machine.current).toBe('viewHolidayEmployees')
-
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
-
- expect(service.machine.current).toBe('viewHolidaySchedule')
- })
-
- it('switches from viewHolidaySchedule to viewHolidayEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
- expect(service.machine.current).toBe('viewHolidaySchedule')
-
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES)
-
- expect(service.machine.current).toBe('viewHolidayEmployees')
- })
- })
-
- describe('back to list', () => {
- it('returns to policyList from viewPolicyDetails', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('returns to policyList from viewPolicyEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('returns to policyList from viewHolidayEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('returns to policyList from viewHolidaySchedule', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- })
- })
-
- describe('cancel transitions', () => {
- it('cancels from policyTypeSelector to policyList', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('cancels from policyDetailsForm to policyList', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('cancels from policySettings to policyList', () => {
- const service = createService()
- toPolicySettings(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('cancels from addEmployeesToPolicy to policyList', () => {
- const service = createService()
- toAddEmployeesToPolicy(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('cancels from holidaySelectionForm to policyList', () => {
- const service = createService()
- toHolidaySelectionForm(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('cancels from addEmployeesHoliday to policyList', () => {
- const service = createService()
- toAddEmployeesHoliday(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
- })
-
- describe('error transitions', () => {
- it('policyDetailsForm error transitions to policyTypeSelector with alert', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create policy' },
- })
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to create policy' }])
- })
-
- it('policyDetailsForm error without alert payload clears alerts', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {})
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('policySettings error transitions to policyDetailsForm with alert', () => {
- const service = createService()
- toPolicySettings(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
- alert: { type: 'error', title: 'Failed to update settings' },
- })
-
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.alerts).toEqual([
- { type: 'error', title: 'Failed to update settings' },
- ])
- })
-
- it('addEmployeesToPolicy error transitions to policySettings with alert', () => {
- const service = createService()
- toAddEmployeesToPolicy(service)
-
- send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees' },
- })
-
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to add employees' }])
- })
-
- it('holidaySelectionForm error transitions to policyTypeSelector with alert', () => {
- const service = createService()
- toHolidaySelectionForm(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create holiday policy' },
- })
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toEqual([
- { type: 'error', title: 'Failed to create holiday policy' },
- ])
- })
-
- it('addEmployeesHoliday error transitions to holidaySelectionForm with alert', () => {
- const service = createService()
- toAddEmployeesHoliday(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees to holiday' },
- })
-
- expect(service.machine.current).toBe('holidaySelectionForm')
- expect(service.context.alerts).toEqual([
- { type: 'error', title: 'Failed to add employees to holiday' },
- ])
- })
- })
-
- describe('alert lifecycle', () => {
- it('forward transition after error clears alerts', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Create failed' },
- })
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toEqual([{ type: 'error', title: 'Create failed' }])
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('cancel clears alerts', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Create failed' },
- })
- expect(service.context.alerts).toBeDefined()
-
- send(service, componentEvents.CANCEL)
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('create policy clears alerts from policyList', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('error then retry full flow preserves correct state', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Create failed' },
- })
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toHaveLength(1)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.alerts).toBeUndefined()
-
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-456' })
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.policyId).toBe('policy-456')
- expect(service.context.alerts).toBeUndefined()
- })
- })
-})
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
deleted file mode 100644
index af33c841d..000000000
--- a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
+++ /dev/null
@@ -1,359 +0,0 @@
-import { transition, reduce, state, guard } from 'robot3'
-import {
- PolicyListContextual,
- PolicyTypeSelectorContextual,
- PolicyDetailsFormContextual,
- PolicySettingsContextual,
- AddEmployeesToPolicyContextual,
- ViewPolicyDetailsContextual,
- ViewPolicyEmployeesContextual,
- HolidaySelectionFormContextual,
- AddEmployeesHolidayContextual,
- ViewHolidayEmployeesContextual,
- ViewHolidayScheduleContextual,
- type TimeOffFlowContextInterface,
- type TimeOffFlowAlert,
-} from './TimeOffFlowComponents'
-import { componentEvents } from '@/shared/constants'
-import type { MachineTransition } from '@/types/Helpers'
-
-type PolicyTypePayload = { policyType: 'sick' | 'vacation' | 'holiday' }
-type PolicyCreatedPayload = { policyId: string }
-type ErrorPayload = { alert?: TimeOffFlowAlert }
-type ViewPolicyPayload = { policyId: string; policyType: 'sick' | 'vacation' | 'holiday' }
-
-function isSickOrVacation(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
- return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
-}
-
-function isHoliday(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
- return ev.payload.policyType === 'holiday'
-}
-
-function isSickOrVacationView(
- _ctx: TimeOffFlowContextInterface,
- ev: { payload: ViewPolicyPayload },
-) {
- return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
-}
-
-function isHolidayView(_ctx: TimeOffFlowContextInterface, ev: { payload: ViewPolicyPayload }) {
- return ev.payload.policyType === 'holiday'
-}
-
-const cancelToPolicyList = transition(
- componentEvents.CANCEL,
- 'policyList',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyListContextual,
- alerts: undefined,
- }),
- ),
-)
-
-const backToListTransition = transition(
- componentEvents.TIME_OFF_BACK_TO_LIST,
- 'policyList',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyListContextual,
- alerts: undefined,
- }),
- ),
-)
-
-export const timeOffMachine = {
- policyList: state(
- transition(
- componentEvents.TIME_OFF_CREATE_POLICY,
- 'policyTypeSelector',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyTypeSelectorContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY,
- 'viewPolicyDetails',
- guard(isSickOrVacationView),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ViewPolicyPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyDetailsContextual,
- policyId: ev.payload.policyId,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY,
- 'viewHolidayEmployees',
- guard(isHolidayView),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ViewPolicyPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayEmployeesContextual,
- policyId: ev.payload.policyId,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- ),
-
- policyTypeSelector: state(
- transition(
- componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
- 'policyDetailsForm',
- guard(isSickOrVacation),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: PolicyTypePayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyDetailsFormContextual,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
- 'holidaySelectionForm',
- guard(isHoliday),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: PolicyTypePayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: HolidaySelectionFormContextual,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- policyDetailsForm: state(
- transition(
- componentEvents.TIME_OFF_POLICY_DETAILS_DONE,
- 'policySettings',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: PolicyCreatedPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicySettingsContextual,
- policyId: ev.payload.policyId,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_POLICY_CREATE_ERROR,
- 'policyTypeSelector',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyTypeSelectorContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- policySettings: state(
- transition(
- componentEvents.TIME_OFF_POLICY_SETTINGS_DONE,
- 'addEmployeesToPolicy',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: AddEmployeesToPolicyContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR,
- 'policyDetailsForm',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyDetailsFormContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- addEmployeesToPolicy: state(
- transition(
- componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE,
- 'viewPolicyDetails',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyDetailsContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR,
- 'policySettings',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicySettingsContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- viewPolicyDetails: state(
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES,
- 'viewPolicyEmployees',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyEmployeesContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- viewPolicyEmployees: state(
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY_DETAILS,
- 'viewPolicyDetails',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyDetailsContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- holidaySelectionForm: state(
- transition(
- componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE,
- 'addEmployeesHoliday',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: AddEmployeesHolidayContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR,
- 'policyTypeSelector',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyTypeSelectorContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- addEmployeesHoliday: state(
- transition(
- componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE,
- 'viewHolidayEmployees',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayEmployeesContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR,
- 'holidaySelectionForm',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: HolidaySelectionFormContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- viewHolidayEmployees: state(
- transition(
- componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE,
- 'viewHolidaySchedule',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayScheduleContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- viewHolidaySchedule: state(
- transition(
- componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES,
- 'viewHolidayEmployees',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayEmployeesContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- final: state(),
-}
diff --git a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
deleted file mode 100644
index d73cea0cd..000000000
--- a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewHolidayEmployeesProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function ViewHolidayEmployees(props: ViewHolidayEmployeesProps) {
- return (
-
-
-
View Holiday Employees (companyId: {props.companyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
- }}
- >
- Holiday Schedule Tab
-
-
{
- props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
- }}
- >
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
deleted file mode 100644
index 23a9786fb..000000000
--- a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewHolidayScheduleProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function ViewHolidaySchedule(props: ViewHolidayScheduleProps) {
- return (
-
-
-
View Holiday Schedule (companyId: {props.companyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES)
- }}
- >
- View Employees Tab
-
-
{
- props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
- }}
- >
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
deleted file mode 100644
index c031a37de..000000000
--- a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewPolicyDetailsProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function ViewPolicyDetails(props: ViewPolicyDetailsProps) {
- return (
-
-
-
View Policy Details (policyId: {props.policyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
- }}
- >
- View Employees Tab
-
-
{
- props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
- }}
- >
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
deleted file mode 100644
index d9bc6306c..000000000
--- a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewPolicyEmployeesProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function ViewPolicyEmployees(props: ViewPolicyEmployeesProps) {
- return (
-
-
-
View Policy Employees (policyId: {props.policyId})
-
{
- props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_DETAILS)
- }}
- >
- Policy Details Tab
-
-
{
- props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST)
- }}
- >
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/index.ts b/src/components/TimeOff/index.ts
deleted file mode 100644
index eac24ad97..000000000
--- a/src/components/TimeOff/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export { PolicyList } from './PolicyList/PolicyList'
-export type { PolicyListProps } from './PolicyList/PolicyList'
-export { PolicyTypeSelector } from './PolicyTypeSelector/PolicyTypeSelector'
-export type { PolicyTypeSelectorProps } from './PolicyTypeSelector/PolicyTypeSelector'
-export { PolicyDetailsForm } from './PolicyDetailsForm/PolicyDetailsForm'
-export type { PolicyDetailsFormProps } from './PolicyDetailsForm/PolicyDetailsForm'
-export { PolicySettings } from './PolicySettings/PolicySettings'
-export type { PolicySettingsProps } from './PolicySettings/PolicySettings'
-export { AddEmployeesToPolicy } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
-export type { AddEmployeesToPolicyProps } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
-export { ViewPolicyDetails } from './ViewPolicyDetails/ViewPolicyDetails'
-export type { ViewPolicyDetailsProps } from './ViewPolicyDetails/ViewPolicyDetails'
-export { ViewPolicyEmployees } from './ViewPolicyEmployees/ViewPolicyEmployees'
-export type { ViewPolicyEmployeesProps } from './ViewPolicyEmployees/ViewPolicyEmployees'
-export { HolidaySelectionForm } from './HolidaySelectionForm/HolidaySelectionForm'
-export type { HolidaySelectionFormProps } from './HolidaySelectionForm/HolidaySelectionForm'
-export { AddEmployeesHoliday } from './AddEmployeesHoliday/AddEmployeesHoliday'
-export type { AddEmployeesHolidayProps } from './AddEmployeesHoliday/AddEmployeesHoliday'
-export { ViewHolidayEmployees } from './ViewHolidayEmployees/ViewHolidayEmployees'
-export type { ViewHolidayEmployeesProps } from './ViewHolidayEmployees/ViewHolidayEmployees'
-export { ViewHolidaySchedule } from './ViewHolidaySchedule/ViewHolidaySchedule'
-export type { ViewHolidayScheduleProps } from './ViewHolidaySchedule/ViewHolidaySchedule'
-export { TimeOffFlow } from './TimeOffFlow/TimeOffFlow'
-export type { TimeOffFlowProps } from './TimeOffFlow/TimeOffFlowComponents'
From e78cdae9d397a860f40f87d24c7fe6ca081b30cb Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Thu, 2 Apr 2026 11:54:52 -0700
Subject: [PATCH 06/10] fix: put back changes
---
.../PolicyListPresentation.module.scss | 6 +
.../PolicyList/PolicyListPresentation.tsx | 153 ++++++++++++++++++
.../PolicyList/PolicyListTypes.ts | 18 +++
3 files changed, 177 insertions(+)
create mode 100644 src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.module.scss
create mode 100644 src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.tsx
create mode 100644 src/components/UNSTABLE_TimeOff/PolicyList/PolicyListTypes.ts
diff --git a/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.module.scss b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.module.scss
new file mode 100644
index 000000000..b33b09745
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.module.scss
@@ -0,0 +1,6 @@
+.actionsCell {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: toRem(12);
+}
diff --git a/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.tsx b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.tsx
new file mode 100644
index 000000000..937ce5100
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.tsx
@@ -0,0 +1,153 @@
+import { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import type { PolicyListPresentationProps, PolicyListItem } from './PolicyListTypes'
+import styles from './PolicyListPresentation.module.scss'
+import {
+ DataView,
+ Flex,
+ EmptyData,
+ ActionsLayout,
+ HamburgerMenu,
+ useDataView,
+} from '@/components/Common'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+import { useI18n } from '@/i18n'
+
+export function PolicyListPresentation({
+ policies,
+ onCreatePolicy,
+ onEditPolicy,
+ onFinishSetup,
+ onDeletePolicy,
+ deleteSuccessAlert,
+ onDismissDeleteAlert,
+ isDeletingPolicyId,
+}: PolicyListPresentationProps) {
+ const { Button, Heading, Text, Alert, Dialog } = useComponentContext()
+ useI18n('Company.TimeOff.TimeOffPolicies')
+ const { t } = useTranslation('Company.TimeOff.TimeOffPolicies')
+
+ const [deletePolicyDialogState, setDeletePolicyDialogState] = useState<{
+ isOpen: boolean
+ policy: PolicyListItem | null
+ }>({
+ isOpen: false,
+ policy: null,
+ })
+
+ const handleOpenDeleteDialog = (policy: PolicyListItem) => {
+ setDeletePolicyDialogState({ isOpen: true, policy })
+ }
+
+ const handleCloseDeleteDialog = () => {
+ setDeletePolicyDialogState({ isOpen: false, policy: null })
+ }
+
+ const handleConfirmDelete = () => {
+ if (deletePolicyDialogState.policy) {
+ onDeletePolicy(deletePolicyDialogState.policy)
+ handleCloseDeleteDialog()
+ }
+ }
+
+ const { ...dataViewProps } = useDataView({
+ data: policies,
+ columns: [
+ {
+ title: t('tableHeaders.name'),
+ render: (policy: PolicyListItem) => {policy.name},
+ },
+ {
+ title: t('tableHeaders.enrolled'),
+ render: (policy: PolicyListItem) => (
+ {policy.enrolledDisplay}
+ ),
+ },
+ ],
+ itemMenu: (policy: PolicyListItem) => {
+ const isDeleting = isDeletingPolicyId === policy.uuid
+
+ return (
+
+ {!policy.isComplete && (
+ {
+ onFinishSetup(policy)
+ }}
+ >
+ {t('finishSetupCta')}
+
+ )}
+ {
+ onEditPolicy(policy)
+ },
+ },
+ {
+ label: t('actions.deletePolicy'),
+ onClick: () => {
+ handleOpenDeleteDialog(policy)
+ },
+ },
+ ]}
+ />
+
+ )
+ },
+ emptyState: () => (
+
+
+
+ {t('createPolicyCta')}
+
+
+
+ ),
+ })
+
+ return (
+
+ {deleteSuccessAlert && (
+
+ )}
+
+
+ {t('pageTitle')}
+ {policies.length > 0 && (
+
+ {t('createPolicyCta')}
+
+ )}
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListTypes.ts b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListTypes.ts
new file mode 100644
index 000000000..775ca1ebc
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListTypes.ts
@@ -0,0 +1,18 @@
+export interface PolicyListItem {
+ uuid: string
+ name: string
+ policyType: string
+ isComplete: boolean
+ enrolledDisplay: string
+}
+
+export interface PolicyListPresentationProps {
+ policies: PolicyListItem[]
+ onCreatePolicy: () => void
+ onEditPolicy: (policy: PolicyListItem) => void
+ onFinishSetup: (policy: PolicyListItem) => void
+ onDeletePolicy: (policy: PolicyListItem) => void
+ deleteSuccessAlert?: string | null
+ onDismissDeleteAlert?: () => void
+ isDeletingPolicyId?: string | null
+}
From e6498ec4658e344760d06b00c94cb341d8c89a0c Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Thu, 26 Mar 2026 16:40:35 -0700
Subject: [PATCH 07/10] feat: state machine and skeleton ui
---
.../AddEmployeesHoliday.tsx | 29 +
.../AddEmployeesToPolicy.tsx | 29 +
.../HolidaySelectionForm.tsx | 29 +
.../PolicyDetailsForm/PolicyDetailsForm.tsx | 38 ++
.../TimeOff/PolicyList/PolicyList.tsx | 39 ++
.../TimeOff/PolicySettings/PolicySettings.tsx | 29 +
.../PolicyTypeSelector/PolicyTypeSelector.tsx | 44 ++
.../TimeOff/TimeOffFlow/TimeOffFlow.tsx | 23 +
.../TimeOffFlow/TimeOffFlowComponents.tsx | 150 +++++
src/components/TimeOff/TimeOffFlow/index.ts | 2 +
.../TimeOffFlow/timeOffStateMachine.test.ts | 562 ++++++++++++++++++
.../TimeOffFlow/timeOffStateMachine.ts | 359 +++++++++++
.../ViewHolidayEmployees.tsx | 22 +
.../ViewHolidaySchedule.tsx | 22 +
.../ViewPolicyDetails/ViewPolicyDetails.tsx | 22 +
.../ViewPolicyEmployees.tsx | 22 +
src/components/TimeOff/index.ts | 24 +
17 files changed, 1445 insertions(+)
create mode 100644 src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
create mode 100644 src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
create mode 100644 src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
create mode 100644 src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
create mode 100644 src/components/TimeOff/PolicyList/PolicyList.tsx
create mode 100644 src/components/TimeOff/PolicySettings/PolicySettings.tsx
create mode 100644 src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
create mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
create mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
create mode 100644 src/components/TimeOff/TimeOffFlow/index.ts
create mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
create mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
create mode 100644 src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
create mode 100644 src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
create mode 100644 src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
create mode 100644 src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
create mode 100644 src/components/TimeOff/index.ts
diff --git a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
new file mode 100644
index 000000000..fdda6176e
--- /dev/null
+++ b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface AddEmployeesHolidayProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function AddEmployeesHoliday(props: AddEmployeesHolidayProps) {
+ return (
+
+
+
Add Employees to Holiday (companyId: {props.companyId})
+
{ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE); }}>
+ Done
+
+
+ { props.onEvent(componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
+ alert: { type: 'error', title: 'Failed to add employees to holiday' },
+ }); }
+ }
+ >
+ Simulate Error
+
+
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
+
+ )
+}
diff --git a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
new file mode 100644
index 000000000..d9b1f970a
--- /dev/null
+++ b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface AddEmployeesToPolicyProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function AddEmployeesToPolicy(props: AddEmployeesToPolicyProps) {
+ return (
+
+
+
Add Employees to Policy + Starting Balances (policyId: {props.policyId})
+
{ props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE); }}>
+ Done
+
+
+ { props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
+ alert: { type: 'error', title: 'Failed to add employees' },
+ }); }
+ }
+ >
+ Simulate Error
+
+
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
+
+ )
+}
diff --git a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
new file mode 100644
index 000000000..00603a872
--- /dev/null
+++ b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface HolidaySelectionFormProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function HolidaySelectionForm(props: HolidaySelectionFormProps) {
+ return (
+
+
+
Holiday Selection Form (companyId: {props.companyId})
+
{ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE); }}>
+ Done
+
+
+ { props.onEvent(componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Failed to create holiday policy' },
+ }); }
+ }
+ >
+ Simulate Error
+
+
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
new file mode 100644
index 000000000..0e796cdf6
--- /dev/null
+++ b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
@@ -0,0 +1,38 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicyDetailsFormProps extends BaseComponentInterface {
+ companyId: string
+ policyType: 'sick' | 'vacation'
+}
+
+export function PolicyDetailsForm(props: PolicyDetailsFormProps) {
+ return (
+
+
+
+ Policy Details Form (type: {props.policyType}, companyId: {props.companyId})
+
+
+ { props.onEvent(componentEvents.TIME_OFF_POLICY_DETAILS_DONE, {
+ policyId: 'mock-policy-id',
+ }); }
+ }
+ >
+ Done
+
+
+ { props.onEvent(componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Failed to create policy' },
+ }); }
+ }
+ >
+ Simulate Error
+
+
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyList/PolicyList.tsx b/src/components/TimeOff/PolicyList/PolicyList.tsx
new file mode 100644
index 000000000..8e2f5afd5
--- /dev/null
+++ b/src/components/TimeOff/PolicyList/PolicyList.tsx
@@ -0,0 +1,39 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicyListProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function PolicyList(props: PolicyListProps) {
+ return (
+
+
+
Policy List (companyId: {props.companyId})
+
{ props.onEvent(componentEvents.TIME_OFF_CREATE_POLICY); }}>
+ Create Policy
+
+
+ { props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'mock-policy-id',
+ policyType: 'vacation',
+ }); }
+ }
+ >
+ View Vacation Policy
+
+
+ { props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'mock-policy-id',
+ policyType: 'holiday',
+ }); }
+ }
+ >
+ View Holiday Policy
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicySettings/PolicySettings.tsx b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
new file mode 100644
index 000000000..c05d05d8e
--- /dev/null
+++ b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
@@ -0,0 +1,29 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicySettingsProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function PolicySettings(props: PolicySettingsProps) {
+ return (
+
+
+
Policy Settings (policyId: {props.policyId})
+
{ props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_DONE); }}>
+ Done
+
+
+ { props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
+ alert: { type: 'error', title: 'Failed to update policy settings' },
+ }); }
+ }
+ >
+ Simulate Error
+
+
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
+
+ )
+}
diff --git a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
new file mode 100644
index 000000000..792fa8c55
--- /dev/null
+++ b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
@@ -0,0 +1,44 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface PolicyTypeSelectorProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function PolicyTypeSelector(props: PolicyTypeSelectorProps) {
+ return (
+
+
+
Policy Type Selector (companyId: {props.companyId})
+
+ { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
+ policyType: 'sick',
+ }); }
+ }
+ >
+ Sick
+
+
+ { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
+ policyType: 'vacation',
+ }); }
+ }
+ >
+ Vacation
+
+
+ { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
+ policyType: 'holiday',
+ }); }
+ }
+ >
+ Company Holiday
+
+
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
+
+
+ )
+}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
new file mode 100644
index 000000000..5b22ec216
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
@@ -0,0 +1,23 @@
+import { createMachine } from 'robot3'
+import { useMemo } from 'react'
+import { timeOffMachine } from './timeOffStateMachine'
+import type { TimeOffFlowProps, TimeOffFlowContextInterface } from './TimeOffFlowComponents'
+import { PolicyListContextual } from './TimeOffFlowComponents'
+import { Flow } from '@/components/Flow/Flow'
+
+export const TimeOffFlow = ({ companyId, onEvent }: TimeOffFlowProps) => {
+ const timeOffFlow = useMemo(
+ () =>
+ createMachine(
+ 'policyList',
+ timeOffMachine,
+ (initialContext: TimeOffFlowContextInterface) => ({
+ ...initialContext,
+ component: PolicyListContextual,
+ companyId,
+ }),
+ ),
+ [companyId],
+ )
+ return
+}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
new file mode 100644
index 000000000..b9c6ca237
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
@@ -0,0 +1,150 @@
+import type { ReactNode } from 'react'
+import { PolicyList } from '../PolicyList/PolicyList'
+import { PolicyTypeSelector } from '../PolicyTypeSelector/PolicyTypeSelector'
+import { PolicyDetailsForm } from '../PolicyDetailsForm/PolicyDetailsForm'
+import { PolicySettings } from '../PolicySettings/PolicySettings'
+import { AddEmployeesToPolicy } from '../AddEmployeesToPolicy/AddEmployeesToPolicy'
+import { ViewPolicyDetails } from '../ViewPolicyDetails/ViewPolicyDetails'
+import { ViewPolicyEmployees } from '../ViewPolicyEmployees/ViewPolicyEmployees'
+import { HolidaySelectionForm } from '../HolidaySelectionForm/HolidaySelectionForm'
+import { AddEmployeesHoliday } from '../AddEmployeesHoliday/AddEmployeesHoliday'
+import { ViewHolidayEmployees } from '../ViewHolidayEmployees/ViewHolidayEmployees'
+import { ViewHolidaySchedule } from '../ViewHolidaySchedule/ViewHolidaySchedule'
+import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow'
+import type { BaseComponentInterface } from '@/components/Base'
+import { Flex } from '@/components/Common'
+import { ensureRequired } from '@/helpers/ensureRequired'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+
+export interface TimeOffFlowProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export type TimeOffFlowAlert = {
+ type: 'error' | 'info' | 'success'
+ title: string
+ content?: ReactNode
+}
+
+export type TimeOffPolicyType = 'sick' | 'vacation' | 'holiday'
+
+export interface TimeOffFlowContextInterface extends FlowContextInterface {
+ companyId: string
+ policyType?: TimeOffPolicyType
+ policyId?: string
+ alerts?: TimeOffFlowAlert[]
+}
+
+export function PolicyListContextual() {
+ const { onEvent, companyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function PolicyTypeSelectorContextual() {
+ const { onEvent, companyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function PolicyDetailsFormContextual() {
+ const { onEvent, companyId, policyType, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function PolicySettingsContextual() {
+ const { onEvent, policyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function AddEmployeesToPolicyContextual() {
+ const { onEvent, policyId } = useFlow()
+ return
+}
+
+export function ViewPolicyDetailsContextual() {
+ const { onEvent, policyId } = useFlow()
+ return
+}
+
+export function ViewPolicyEmployeesContextual() {
+ const { onEvent, policyId } = useFlow()
+ return
+}
+
+export function HolidaySelectionFormContextual() {
+ const { onEvent, companyId, alerts } = useFlow()
+ const { Alert } = useComponentContext()
+
+ return (
+
+ {alerts?.map((alert, index) => (
+
+ {alert.content}
+
+ ))}
+
+
+ )
+}
+
+export function AddEmployeesHolidayContextual() {
+ const { onEvent, companyId } = useFlow()
+ return
+}
+
+export function ViewHolidayEmployeesContextual() {
+ const { onEvent, companyId } = useFlow()
+ return
+}
+
+export function ViewHolidayScheduleContextual() {
+ const { onEvent, companyId } = useFlow()
+ return
+}
diff --git a/src/components/TimeOff/TimeOffFlow/index.ts b/src/components/TimeOff/TimeOffFlow/index.ts
new file mode 100644
index 000000000..db1e7c9b2
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/index.ts
@@ -0,0 +1,2 @@
+export { TimeOffFlow } from './TimeOffFlow'
+export type { TimeOffFlowProps } from './TimeOffFlowComponents'
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
new file mode 100644
index 000000000..a4268c98c
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
@@ -0,0 +1,562 @@
+import { describe, expect, it } from 'vitest'
+import { createMachine, interpret, type SendFunction } from 'robot3'
+import { timeOffMachine } from './timeOffStateMachine'
+import type { TimeOffFlowContextInterface } from './TimeOffFlowComponents'
+import { componentEvents } from '@/shared/constants'
+
+type TimeOffState =
+ | 'policyList'
+ | 'policyTypeSelector'
+ | 'policyDetailsForm'
+ | 'policySettings'
+ | 'addEmployeesToPolicy'
+ | 'viewPolicyDetails'
+ | 'viewPolicyEmployees'
+ | 'holidaySelectionForm'
+ | 'addEmployeesHoliday'
+ | 'viewHolidayEmployees'
+ | 'viewHolidaySchedule'
+ | 'final'
+
+function createTestMachine(initialState: TimeOffState = 'policyList') {
+ return createMachine(
+ initialState,
+ timeOffMachine,
+ (initialContext: TimeOffFlowContextInterface) => ({
+ ...initialContext,
+ component: () => null,
+ companyId: 'company-123',
+ }),
+ )
+}
+
+function createService(initialState: TimeOffState = 'policyList') {
+ const machine = createTestMachine(initialState)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return interpret(machine, () => {}, {} as any)
+}
+
+function send(service: ReturnType, type: string, payload?: unknown) {
+ ;(service.send as SendFunction)({ type, payload })
+}
+
+function toPolicyTypeSelector(service: ReturnType) {
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ expect(service.machine.current).toBe('policyTypeSelector')
+}
+
+function toPolicyDetailsForm(service: ReturnType) {
+ toPolicyTypeSelector(service)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+}
+
+function toPolicySettings(service: ReturnType) {
+ toPolicyDetailsForm(service)
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
+ expect(service.machine.current).toBe('policySettings')
+}
+
+function toAddEmployeesToPolicy(service: ReturnType) {
+ toPolicySettings(service)
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+ expect(service.machine.current).toBe('addEmployeesToPolicy')
+}
+
+function toHolidaySelectionForm(service: ReturnType) {
+ toPolicyTypeSelector(service)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
+ expect(service.machine.current).toBe('holidaySelectionForm')
+}
+
+function toAddEmployeesHoliday(service: ReturnType) {
+ toHolidaySelectionForm(service)
+ send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+ expect(service.machine.current).toBe('addEmployeesHoliday')
+}
+
+describe('timeOffStateMachine', () => {
+ describe('policyList state', () => {
+ it('starts in policyList by default', () => {
+ const service = createService()
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('transitions to policyTypeSelector on TIME_OFF_CREATE_POLICY', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to viewPolicyDetails on TIME_OFF_VIEW_POLICY with sick/vacation type', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-456',
+ policyType: 'vacation',
+ })
+
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ expect(service.context.policyId).toBe('policy-456')
+ expect(service.context.policyType).toBe('vacation')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to viewHolidayEmployees on TIME_OFF_VIEW_POLICY with holiday type', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-789',
+ policyType: 'holiday',
+ })
+
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ expect(service.context.policyId).toBe('policy-789')
+ expect(service.context.policyType).toBe('holiday')
+ expect(service.context.alerts).toBeUndefined()
+ })
+ })
+
+ describe('policyTypeSelector state', () => {
+ it('transitions to policyDetailsForm on sick type selection', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
+
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.policyType).toBe('sick')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to policyDetailsForm on vacation type selection', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.policyType).toBe('vacation')
+ })
+
+ it('transitions to holidaySelectionForm on holiday type selection', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
+
+ expect(service.machine.current).toBe('holidaySelectionForm')
+ expect(service.context.policyType).toBe('holiday')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions to policyList on CANCEL', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+ })
+
+ describe('sick/vacation creation flow', () => {
+ it('transitions policyDetailsForm -> policySettings on POLICY_DETAILS_DONE', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
+
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.policyId).toBe('policy-123')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions policySettings -> addEmployeesToPolicy on POLICY_SETTINGS_DONE', () => {
+ const service = createService()
+ toPolicySettings(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+
+ expect(service.machine.current).toBe('addEmployeesToPolicy')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions addEmployeesToPolicy -> viewPolicyDetails on ADD_EMPLOYEES_DONE', () => {
+ const service = createService()
+ toAddEmployeesToPolicy(service)
+
+ send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
+
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('supports full happy path: policyList -> viewPolicyDetails', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ expect(service.machine.current).toBe('policyTypeSelector')
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.policyType).toBe('vacation')
+
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.policyId).toBe('policy-123')
+
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
+ expect(service.machine.current).toBe('addEmployeesToPolicy')
+
+ send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ expect(service.context.policyId).toBe('policy-123')
+ })
+ })
+
+ describe('holiday creation flow', () => {
+ it('transitions holidaySelectionForm -> addEmployeesHoliday on HOLIDAY_SELECTION_DONE', () => {
+ const service = createService()
+ toHolidaySelectionForm(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+
+ expect(service.machine.current).toBe('addEmployeesHoliday')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('transitions addEmployeesHoliday -> viewHolidayEmployees on HOLIDAY_ADD_EMPLOYEES_DONE', () => {
+ const service = createService()
+ toAddEmployeesHoliday(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
+
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('supports full happy path: policyList -> viewHolidayEmployees', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
+ expect(service.machine.current).toBe('holidaySelectionForm')
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
+ expect(service.machine.current).toBe('addEmployeesHoliday')
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ })
+ })
+
+ describe('tab switching', () => {
+ it('switches from viewPolicyDetails to viewPolicyEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+ expect(service.machine.current).toBe('viewPolicyDetails')
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+
+ expect(service.machine.current).toBe('viewPolicyEmployees')
+ })
+
+ it('switches from viewPolicyEmployees to viewPolicyDetails', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+ expect(service.machine.current).toBe('viewPolicyEmployees')
+
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_DETAILS)
+
+ expect(service.machine.current).toBe('viewPolicyDetails')
+ })
+
+ it('switches from viewHolidayEmployees to viewHolidaySchedule', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+
+ expect(service.machine.current).toBe('viewHolidaySchedule')
+ })
+
+ it('switches from viewHolidaySchedule to viewHolidayEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+ expect(service.machine.current).toBe('viewHolidaySchedule')
+
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES)
+
+ expect(service.machine.current).toBe('viewHolidayEmployees')
+ })
+ })
+
+ describe('back to list', () => {
+ it('returns to policyList from viewPolicyDetails', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('returns to policyList from viewPolicyEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'vacation',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('returns to policyList from viewHolidayEmployees', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('returns to policyList from viewHolidaySchedule', () => {
+ const service = createService()
+ send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
+ policyId: 'policy-123',
+ policyType: 'holiday',
+ })
+ send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
+
+ send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+ })
+
+ describe('cancel transitions', () => {
+ it('cancels from policyTypeSelector to policyList', () => {
+ const service = createService()
+ toPolicyTypeSelector(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('cancels from policyDetailsForm to policyList', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('cancels from policySettings to policyList', () => {
+ const service = createService()
+ toPolicySettings(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('cancels from addEmployeesToPolicy to policyList', () => {
+ const service = createService()
+ toAddEmployeesToPolicy(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('cancels from holidaySelectionForm to policyList', () => {
+ const service = createService()
+ toHolidaySelectionForm(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+
+ it('cancels from addEmployeesHoliday to policyList', () => {
+ const service = createService()
+ toAddEmployeesHoliday(service)
+
+ send(service, componentEvents.CANCEL)
+
+ expect(service.machine.current).toBe('policyList')
+ })
+ })
+
+ describe('error transitions', () => {
+ it('policyDetailsForm error transitions to policyTypeSelector with alert', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Failed to create policy' },
+ })
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to create policy' }])
+ })
+
+ it('policyDetailsForm error without alert payload clears alerts', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {})
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('policySettings error transitions to policyDetailsForm with alert', () => {
+ const service = createService()
+ toPolicySettings(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
+ alert: { type: 'error', title: 'Failed to update settings' },
+ })
+
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.alerts).toEqual([
+ { type: 'error', title: 'Failed to update settings' },
+ ])
+ })
+
+ it('addEmployeesToPolicy error transitions to policySettings with alert', () => {
+ const service = createService()
+ toAddEmployeesToPolicy(service)
+
+ send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
+ alert: { type: 'error', title: 'Failed to add employees' },
+ })
+
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to add employees' }])
+ })
+
+ it('holidaySelectionForm error transitions to policyTypeSelector with alert', () => {
+ const service = createService()
+ toHolidaySelectionForm(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Failed to create holiday policy' },
+ })
+
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toEqual([
+ { type: 'error', title: 'Failed to create holiday policy' },
+ ])
+ })
+
+ it('addEmployeesHoliday error transitions to holidaySelectionForm with alert', () => {
+ const service = createService()
+ toAddEmployeesHoliday(service)
+
+ send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
+ alert: { type: 'error', title: 'Failed to add employees to holiday' },
+ })
+
+ expect(service.machine.current).toBe('holidaySelectionForm')
+ expect(service.context.alerts).toEqual([
+ { type: 'error', title: 'Failed to add employees to holiday' },
+ ])
+ })
+ })
+
+ describe('alert lifecycle', () => {
+ it('forward transition after error clears alerts', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Create failed' },
+ })
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toEqual([{ type: 'error', title: 'Create failed' }])
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('cancel clears alerts', () => {
+ const service = createService()
+ toPolicyDetailsForm(service)
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Create failed' },
+ })
+ expect(service.context.alerts).toBeDefined()
+
+ send(service, componentEvents.CANCEL)
+ expect(service.machine.current).toBe('policyList')
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('create policy clears alerts from policyList', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ expect(service.context.alerts).toBeUndefined()
+ })
+
+ it('error then retry full flow preserves correct state', () => {
+ const service = createService()
+
+ send(service, componentEvents.TIME_OFF_CREATE_POLICY)
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+
+ send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
+ alert: { type: 'error', title: 'Create failed' },
+ })
+ expect(service.machine.current).toBe('policyTypeSelector')
+ expect(service.context.alerts).toHaveLength(1)
+
+ send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
+ expect(service.machine.current).toBe('policyDetailsForm')
+ expect(service.context.alerts).toBeUndefined()
+
+ send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-456' })
+ expect(service.machine.current).toBe('policySettings')
+ expect(service.context.policyId).toBe('policy-456')
+ expect(service.context.alerts).toBeUndefined()
+ })
+ })
+})
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
new file mode 100644
index 000000000..af33c841d
--- /dev/null
+++ b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
@@ -0,0 +1,359 @@
+import { transition, reduce, state, guard } from 'robot3'
+import {
+ PolicyListContextual,
+ PolicyTypeSelectorContextual,
+ PolicyDetailsFormContextual,
+ PolicySettingsContextual,
+ AddEmployeesToPolicyContextual,
+ ViewPolicyDetailsContextual,
+ ViewPolicyEmployeesContextual,
+ HolidaySelectionFormContextual,
+ AddEmployeesHolidayContextual,
+ ViewHolidayEmployeesContextual,
+ ViewHolidayScheduleContextual,
+ type TimeOffFlowContextInterface,
+ type TimeOffFlowAlert,
+} from './TimeOffFlowComponents'
+import { componentEvents } from '@/shared/constants'
+import type { MachineTransition } from '@/types/Helpers'
+
+type PolicyTypePayload = { policyType: 'sick' | 'vacation' | 'holiday' }
+type PolicyCreatedPayload = { policyId: string }
+type ErrorPayload = { alert?: TimeOffFlowAlert }
+type ViewPolicyPayload = { policyId: string; policyType: 'sick' | 'vacation' | 'holiday' }
+
+function isSickOrVacation(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
+ return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
+}
+
+function isHoliday(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
+ return ev.payload.policyType === 'holiday'
+}
+
+function isSickOrVacationView(
+ _ctx: TimeOffFlowContextInterface,
+ ev: { payload: ViewPolicyPayload },
+) {
+ return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
+}
+
+function isHolidayView(_ctx: TimeOffFlowContextInterface, ev: { payload: ViewPolicyPayload }) {
+ return ev.payload.policyType === 'holiday'
+}
+
+const cancelToPolicyList = transition(
+ componentEvents.CANCEL,
+ 'policyList',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyListContextual,
+ alerts: undefined,
+ }),
+ ),
+)
+
+const backToListTransition = transition(
+ componentEvents.TIME_OFF_BACK_TO_LIST,
+ 'policyList',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyListContextual,
+ alerts: undefined,
+ }),
+ ),
+)
+
+export const timeOffMachine = {
+ policyList: state(
+ transition(
+ componentEvents.TIME_OFF_CREATE_POLICY,
+ 'policyTypeSelector',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyTypeSelectorContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY,
+ 'viewPolicyDetails',
+ guard(isSickOrVacationView),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ViewPolicyPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyDetailsContextual,
+ policyId: ev.payload.policyId,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY,
+ 'viewHolidayEmployees',
+ guard(isHolidayView),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ViewPolicyPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayEmployeesContextual,
+ policyId: ev.payload.policyId,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ ),
+
+ policyTypeSelector: state(
+ transition(
+ componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
+ 'policyDetailsForm',
+ guard(isSickOrVacation),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: PolicyTypePayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyDetailsFormContextual,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
+ 'holidaySelectionForm',
+ guard(isHoliday),
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: PolicyTypePayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: HolidaySelectionFormContextual,
+ policyType: ev.payload.policyType,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ policyDetailsForm: state(
+ transition(
+ componentEvents.TIME_OFF_POLICY_DETAILS_DONE,
+ 'policySettings',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: PolicyCreatedPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicySettingsContextual,
+ policyId: ev.payload.policyId,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_POLICY_CREATE_ERROR,
+ 'policyTypeSelector',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyTypeSelectorContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ policySettings: state(
+ transition(
+ componentEvents.TIME_OFF_POLICY_SETTINGS_DONE,
+ 'addEmployeesToPolicy',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: AddEmployeesToPolicyContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR,
+ 'policyDetailsForm',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyDetailsFormContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ addEmployeesToPolicy: state(
+ transition(
+ componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE,
+ 'viewPolicyDetails',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyDetailsContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR,
+ 'policySettings',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicySettingsContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ viewPolicyDetails: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES,
+ 'viewPolicyEmployees',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyEmployeesContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ viewPolicyEmployees: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_POLICY_DETAILS,
+ 'viewPolicyDetails',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewPolicyDetailsContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ holidaySelectionForm: state(
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE,
+ 'addEmployeesHoliday',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: AddEmployeesHolidayContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR,
+ 'policyTypeSelector',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: PolicyTypeSelectorContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ addEmployeesHoliday: state(
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE,
+ 'viewHolidayEmployees',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayEmployeesContextual,
+ alerts: undefined,
+ }),
+ ),
+ ),
+ transition(
+ componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR,
+ 'holidaySelectionForm',
+ reduce(
+ (
+ ctx: TimeOffFlowContextInterface,
+ ev: { payload: ErrorPayload },
+ ): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: HolidaySelectionFormContextual,
+ alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
+ }),
+ ),
+ ),
+ cancelToPolicyList,
+ ),
+
+ viewHolidayEmployees: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE,
+ 'viewHolidaySchedule',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayScheduleContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ viewHolidaySchedule: state(
+ transition(
+ componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES,
+ 'viewHolidayEmployees',
+ reduce(
+ (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
+ ...ctx,
+ component: ViewHolidayEmployeesContextual,
+ }),
+ ),
+ ),
+ backToListTransition,
+ ),
+
+ final: state(),
+}
diff --git a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
new file mode 100644
index 000000000..65d6fc30b
--- /dev/null
+++ b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewHolidayEmployeesProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function ViewHolidayEmployees(props: ViewHolidayEmployeesProps) {
+ return (
+
+
+
View Holiday Employees (companyId: {props.companyId})
+
{ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE); }}>
+ Holiday Schedule Tab
+
+
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ Back to List
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
new file mode 100644
index 000000000..6bf6eea02
--- /dev/null
+++ b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewHolidayScheduleProps extends BaseComponentInterface {
+ companyId: string
+}
+
+export function ViewHolidaySchedule(props: ViewHolidayScheduleProps) {
+ return (
+
+
+
View Holiday Schedule (companyId: {props.companyId})
+
{ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES); }}>
+ View Employees Tab
+
+
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ Back to List
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
new file mode 100644
index 000000000..4a7e3d43c
--- /dev/null
+++ b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewPolicyDetailsProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function ViewPolicyDetails(props: ViewPolicyDetailsProps) {
+ return (
+
+
+
View Policy Details (policyId: {props.policyId})
+
{ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES); }}>
+ View Employees Tab
+
+
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ Back to List
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
new file mode 100644
index 000000000..4c92bb122
--- /dev/null
+++ b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
@@ -0,0 +1,22 @@
+import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
+import { componentEvents } from '@/shared/constants'
+
+export interface ViewPolicyEmployeesProps extends BaseComponentInterface {
+ policyId: string
+}
+
+export function ViewPolicyEmployees(props: ViewPolicyEmployeesProps) {
+ return (
+
+
+
View Policy Employees (policyId: {props.policyId})
+
{ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_DETAILS); }}>
+ Policy Details Tab
+
+
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
+ Back to List
+
+
+
+ )
+}
diff --git a/src/components/TimeOff/index.ts b/src/components/TimeOff/index.ts
new file mode 100644
index 000000000..eac24ad97
--- /dev/null
+++ b/src/components/TimeOff/index.ts
@@ -0,0 +1,24 @@
+export { PolicyList } from './PolicyList/PolicyList'
+export type { PolicyListProps } from './PolicyList/PolicyList'
+export { PolicyTypeSelector } from './PolicyTypeSelector/PolicyTypeSelector'
+export type { PolicyTypeSelectorProps } from './PolicyTypeSelector/PolicyTypeSelector'
+export { PolicyDetailsForm } from './PolicyDetailsForm/PolicyDetailsForm'
+export type { PolicyDetailsFormProps } from './PolicyDetailsForm/PolicyDetailsForm'
+export { PolicySettings } from './PolicySettings/PolicySettings'
+export type { PolicySettingsProps } from './PolicySettings/PolicySettings'
+export { AddEmployeesToPolicy } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
+export type { AddEmployeesToPolicyProps } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
+export { ViewPolicyDetails } from './ViewPolicyDetails/ViewPolicyDetails'
+export type { ViewPolicyDetailsProps } from './ViewPolicyDetails/ViewPolicyDetails'
+export { ViewPolicyEmployees } from './ViewPolicyEmployees/ViewPolicyEmployees'
+export type { ViewPolicyEmployeesProps } from './ViewPolicyEmployees/ViewPolicyEmployees'
+export { HolidaySelectionForm } from './HolidaySelectionForm/HolidaySelectionForm'
+export type { HolidaySelectionFormProps } from './HolidaySelectionForm/HolidaySelectionForm'
+export { AddEmployeesHoliday } from './AddEmployeesHoliday/AddEmployeesHoliday'
+export type { AddEmployeesHolidayProps } from './AddEmployeesHoliday/AddEmployeesHoliday'
+export { ViewHolidayEmployees } from './ViewHolidayEmployees/ViewHolidayEmployees'
+export type { ViewHolidayEmployeesProps } from './ViewHolidayEmployees/ViewHolidayEmployees'
+export { ViewHolidaySchedule } from './ViewHolidaySchedule/ViewHolidaySchedule'
+export type { ViewHolidayScheduleProps } from './ViewHolidaySchedule/ViewHolidaySchedule'
+export { TimeOffFlow } from './TimeOffFlow/TimeOffFlow'
+export type { TimeOffFlowProps } from './TimeOffFlow/TimeOffFlowComponents'
From cdf5dbac7d337d1a4f7864153596633793476fd7 Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Mon, 30 Mar 2026 13:30:59 -0700
Subject: [PATCH 08/10] test(SDK-551): add PolicyListPresentation Storybook
stories
Stories covering all visual states: default (with incomplete policy),
empty state, post-delete success alert, and deleting-in-progress.
Made-with: Cursor
---
.../PolicyListPresentation.stories.tsx | 84 +++++++++++++++++++
1 file changed, 84 insertions(+)
create mode 100644 src/components/TimeOff/PolicyList/PolicyListPresentation.stories.tsx
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.stories.tsx b/src/components/TimeOff/PolicyList/PolicyListPresentation.stories.tsx
new file mode 100644
index 000000000..225d925ad
--- /dev/null
+++ b/src/components/TimeOff/PolicyList/PolicyListPresentation.stories.tsx
@@ -0,0 +1,84 @@
+import { fn } from 'storybook/test'
+import { PolicyListPresentation } from './PolicyListPresentation'
+import type { PolicyListItem } from './PolicyListTypes'
+
+export default {
+ title: 'Domain/TimeOff/PolicyList',
+}
+
+const onCreatePolicy = fn().mockName('onCreatePolicy')
+const onEditPolicy = fn().mockName('onEditPolicy')
+const onFinishSetup = fn().mockName('onFinishSetup')
+const onDeletePolicy = fn().mockName('onDeletePolicy')
+const onDismissDeleteAlert = fn().mockName('onDismissDeleteAlert')
+
+const completePolicies: PolicyListItem[] = [
+ {
+ uuid: 'policy-1',
+ name: 'Paid Time Off Policy',
+ policyType: 'vacation',
+ isComplete: true,
+ enrolledDisplay: 'All employees',
+ },
+ {
+ uuid: 'policy-2',
+ name: 'Sick Policy',
+ policyType: 'sick',
+ isComplete: true,
+ enrolledDisplay: '16',
+ },
+]
+
+const incompletePolicies: PolicyListItem[] = [
+ ...completePolicies,
+ {
+ uuid: 'policy-3',
+ name: 'Company PTO policy',
+ policyType: 'vacation',
+ isComplete: false,
+ enrolledDisplay: '\u2013',
+ },
+]
+
+export const Default = () => (
+
+)
+
+export const EmptyState = () => (
+
+)
+
+export const WithDeleteSuccessAlert = () => (
+
+)
+
+export const DeletingPolicy = () => (
+
+)
From 4125b650be678fde01b875bb896df489bfe1a24c Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Mon, 30 Mar 2026 14:27:29 -0700
Subject: [PATCH 09/10] fix: look and feel of table
---
.../Common/EmptyData/EmptyData.module.scss | 4 ++++
src/components/Common/EmptyData/EmptyData.tsx | 19 ++++++++++++-------
2 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/src/components/Common/EmptyData/EmptyData.module.scss b/src/components/Common/EmptyData/EmptyData.module.scss
index 69ec8a9ab..21cffc7be 100644
--- a/src/components/Common/EmptyData/EmptyData.module.scss
+++ b/src/components/Common/EmptyData/EmptyData.module.scss
@@ -6,6 +6,10 @@
width: 100%;
}
+.textContent {
+ text-align: center;
+}
+
.title {
font-size: var(--g-fontSizeHeading3);
font-weight: var(--g-fontWeightMedium);
diff --git a/src/components/Common/EmptyData/EmptyData.tsx b/src/components/Common/EmptyData/EmptyData.tsx
index 3157054a2..da5aea5c4 100644
--- a/src/components/Common/EmptyData/EmptyData.tsx
+++ b/src/components/Common/EmptyData/EmptyData.tsx
@@ -8,20 +8,25 @@ type EmptyDataProps = {
title?: string
description?: string
children?: React.ReactNode
+ icon?: React.ReactNode
}
-export function EmptyData({ title, description, children }: EmptyDataProps) {
+export function EmptyData({ title, description, children, icon }: EmptyDataProps) {
const { t } = useTranslation()
const { Text } = useComponentContext()
return (
-
- {title && (
-
- {title}
-
+ {icon || (
+
)}
- {description && {description}}
+
+ {title && (
+
+ {title}
+
+ )}
+ {description && {description}}
+
{children && children}
From b7242198e8a1cfe0436d1d97b248a7f43b5cab44 Mon Sep 17 00:00:00 2001
From: Kristine White
Date: Thu, 2 Apr 2026 12:16:53 -0700
Subject: [PATCH 10/10] fix: fixed rebase mistake
---
.../AddEmployeesHoliday.tsx | 29 -
.../AddEmployeesToPolicy.tsx | 29 -
.../HolidaySelectionForm.tsx | 29 -
.../PolicyDetailsForm/PolicyDetailsForm.tsx | 38 --
.../TimeOff/PolicyList/PolicyList.tsx | 39 --
.../TimeOff/PolicySettings/PolicySettings.tsx | 29 -
.../PolicyTypeSelector/PolicyTypeSelector.tsx | 44 --
.../TimeOff/TimeOffFlow/TimeOffFlow.tsx | 23 -
.../TimeOffFlow/TimeOffFlowComponents.tsx | 150 -----
src/components/TimeOff/TimeOffFlow/index.ts | 2 -
.../TimeOffFlow/timeOffStateMachine.test.ts | 562 ------------------
.../TimeOffFlow/timeOffStateMachine.ts | 359 -----------
.../ViewHolidayEmployees.tsx | 22 -
.../ViewHolidaySchedule.tsx | 22 -
.../ViewPolicyDetails/ViewPolicyDetails.tsx | 22 -
.../ViewPolicyEmployees.tsx | 22 -
src/components/TimeOff/index.ts | 24 -
.../PolicyListPresentation.stories.tsx | 0
18 files changed, 1445 deletions(-)
delete mode 100644 src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
delete mode 100644 src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
delete mode 100644 src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
delete mode 100644 src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
delete mode 100644 src/components/TimeOff/PolicyList/PolicyList.tsx
delete mode 100644 src/components/TimeOff/PolicySettings/PolicySettings.tsx
delete mode 100644 src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
delete mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
delete mode 100644 src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
delete mode 100644 src/components/TimeOff/TimeOffFlow/index.ts
delete mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
delete mode 100644 src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
delete mode 100644 src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
delete mode 100644 src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
delete mode 100644 src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
delete mode 100644 src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
delete mode 100644 src/components/TimeOff/index.ts
rename src/components/{TimeOff => UNSTABLE_TimeOff}/PolicyList/PolicyListPresentation.stories.tsx (100%)
diff --git a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx b/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
deleted file mode 100644
index fdda6176e..000000000
--- a/src/components/TimeOff/AddEmployeesHoliday/AddEmployeesHoliday.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface AddEmployeesHolidayProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function AddEmployeesHoliday(props: AddEmployeesHolidayProps) {
- return (
-
-
-
Add Employees to Holiday (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE); }}>
- Done
-
-
- { props.onEvent(componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees to holiday' },
- }); }
- }
- >
- Simulate Error
-
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
-
-
- )
-}
diff --git a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx b/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
deleted file mode 100644
index d9b1f970a..000000000
--- a/src/components/TimeOff/AddEmployeesToPolicy/AddEmployeesToPolicy.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface AddEmployeesToPolicyProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function AddEmployeesToPolicy(props: AddEmployeesToPolicyProps) {
- return (
-
-
-
Add Employees to Policy + Starting Balances (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE); }}>
- Done
-
-
- { props.onEvent(componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees' },
- }); }
- }
- >
- Simulate Error
-
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
-
-
- )
-}
diff --git a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx b/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
deleted file mode 100644
index 00603a872..000000000
--- a/src/components/TimeOff/HolidaySelectionForm/HolidaySelectionForm.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface HolidaySelectionFormProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function HolidaySelectionForm(props: HolidaySelectionFormProps) {
- return (
-
-
-
Holiday Selection Form (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE); }}>
- Done
-
-
- { props.onEvent(componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create holiday policy' },
- }); }
- }
- >
- Simulate Error
-
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx b/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
deleted file mode 100644
index 0e796cdf6..000000000
--- a/src/components/TimeOff/PolicyDetailsForm/PolicyDetailsForm.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicyDetailsFormProps extends BaseComponentInterface {
- companyId: string
- policyType: 'sick' | 'vacation'
-}
-
-export function PolicyDetailsForm(props: PolicyDetailsFormProps) {
- return (
-
-
-
- Policy Details Form (type: {props.policyType}, companyId: {props.companyId})
-
-
- { props.onEvent(componentEvents.TIME_OFF_POLICY_DETAILS_DONE, {
- policyId: 'mock-policy-id',
- }); }
- }
- >
- Done
-
-
- { props.onEvent(componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create policy' },
- }); }
- }
- >
- Simulate Error
-
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyList/PolicyList.tsx b/src/components/TimeOff/PolicyList/PolicyList.tsx
deleted file mode 100644
index 8e2f5afd5..000000000
--- a/src/components/TimeOff/PolicyList/PolicyList.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicyListProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function PolicyList(props: PolicyListProps) {
- return (
-
-
-
Policy List (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_CREATE_POLICY); }}>
- Create Policy
-
-
- { props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'mock-policy-id',
- policyType: 'vacation',
- }); }
- }
- >
- View Vacation Policy
-
-
- { props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'mock-policy-id',
- policyType: 'holiday',
- }); }
- }
- >
- View Holiday Policy
-
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicySettings/PolicySettings.tsx b/src/components/TimeOff/PolicySettings/PolicySettings.tsx
deleted file mode 100644
index c05d05d8e..000000000
--- a/src/components/TimeOff/PolicySettings/PolicySettings.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicySettingsProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function PolicySettings(props: PolicySettingsProps) {
- return (
-
-
-
Policy Settings (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_DONE); }}>
- Done
-
-
- { props.onEvent(componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
- alert: { type: 'error', title: 'Failed to update policy settings' },
- }); }
- }
- >
- Simulate Error
-
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
-
-
- )
-}
diff --git a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx b/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
deleted file mode 100644
index 792fa8c55..000000000
--- a/src/components/TimeOff/PolicyTypeSelector/PolicyTypeSelector.tsx
+++ /dev/null
@@ -1,44 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface PolicyTypeSelectorProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function PolicyTypeSelector(props: PolicyTypeSelectorProps) {
- return (
-
-
-
Policy Type Selector (companyId: {props.companyId})
-
- { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
- policyType: 'sick',
- }); }
- }
- >
- Sick
-
-
- { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
- policyType: 'vacation',
- }); }
- }
- >
- Vacation
-
-
- { props.onEvent(componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, {
- policyType: 'holiday',
- }); }
- }
- >
- Company Holiday
-
-
{ props.onEvent(componentEvents.CANCEL); }}>Cancel
-
-
- )
-}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
deleted file mode 100644
index 5b22ec216..000000000
--- a/src/components/TimeOff/TimeOffFlow/TimeOffFlow.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import { createMachine } from 'robot3'
-import { useMemo } from 'react'
-import { timeOffMachine } from './timeOffStateMachine'
-import type { TimeOffFlowProps, TimeOffFlowContextInterface } from './TimeOffFlowComponents'
-import { PolicyListContextual } from './TimeOffFlowComponents'
-import { Flow } from '@/components/Flow/Flow'
-
-export const TimeOffFlow = ({ companyId, onEvent }: TimeOffFlowProps) => {
- const timeOffFlow = useMemo(
- () =>
- createMachine(
- 'policyList',
- timeOffMachine,
- (initialContext: TimeOffFlowContextInterface) => ({
- ...initialContext,
- component: PolicyListContextual,
- companyId,
- }),
- ),
- [companyId],
- )
- return
-}
diff --git a/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx b/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
deleted file mode 100644
index b9c6ca237..000000000
--- a/src/components/TimeOff/TimeOffFlow/TimeOffFlowComponents.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import type { ReactNode } from 'react'
-import { PolicyList } from '../PolicyList/PolicyList'
-import { PolicyTypeSelector } from '../PolicyTypeSelector/PolicyTypeSelector'
-import { PolicyDetailsForm } from '../PolicyDetailsForm/PolicyDetailsForm'
-import { PolicySettings } from '../PolicySettings/PolicySettings'
-import { AddEmployeesToPolicy } from '../AddEmployeesToPolicy/AddEmployeesToPolicy'
-import { ViewPolicyDetails } from '../ViewPolicyDetails/ViewPolicyDetails'
-import { ViewPolicyEmployees } from '../ViewPolicyEmployees/ViewPolicyEmployees'
-import { HolidaySelectionForm } from '../HolidaySelectionForm/HolidaySelectionForm'
-import { AddEmployeesHoliday } from '../AddEmployeesHoliday/AddEmployeesHoliday'
-import { ViewHolidayEmployees } from '../ViewHolidayEmployees/ViewHolidayEmployees'
-import { ViewHolidaySchedule } from '../ViewHolidaySchedule/ViewHolidaySchedule'
-import { useFlow, type FlowContextInterface } from '@/components/Flow/useFlow'
-import type { BaseComponentInterface } from '@/components/Base'
-import { Flex } from '@/components/Common'
-import { ensureRequired } from '@/helpers/ensureRequired'
-import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
-
-export interface TimeOffFlowProps extends BaseComponentInterface {
- companyId: string
-}
-
-export type TimeOffFlowAlert = {
- type: 'error' | 'info' | 'success'
- title: string
- content?: ReactNode
-}
-
-export type TimeOffPolicyType = 'sick' | 'vacation' | 'holiday'
-
-export interface TimeOffFlowContextInterface extends FlowContextInterface {
- companyId: string
- policyType?: TimeOffPolicyType
- policyId?: string
- alerts?: TimeOffFlowAlert[]
-}
-
-export function PolicyListContextual() {
- const { onEvent, companyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function PolicyTypeSelectorContextual() {
- const { onEvent, companyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function PolicyDetailsFormContextual() {
- const { onEvent, companyId, policyType, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function PolicySettingsContextual() {
- const { onEvent, policyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function AddEmployeesToPolicyContextual() {
- const { onEvent, policyId } = useFlow()
- return
-}
-
-export function ViewPolicyDetailsContextual() {
- const { onEvent, policyId } = useFlow()
- return
-}
-
-export function ViewPolicyEmployeesContextual() {
- const { onEvent, policyId } = useFlow()
- return
-}
-
-export function HolidaySelectionFormContextual() {
- const { onEvent, companyId, alerts } = useFlow()
- const { Alert } = useComponentContext()
-
- return (
-
- {alerts?.map((alert, index) => (
-
- {alert.content}
-
- ))}
-
-
- )
-}
-
-export function AddEmployeesHolidayContextual() {
- const { onEvent, companyId } = useFlow()
- return
-}
-
-export function ViewHolidayEmployeesContextual() {
- const { onEvent, companyId } = useFlow()
- return
-}
-
-export function ViewHolidayScheduleContextual() {
- const { onEvent, companyId } = useFlow()
- return
-}
diff --git a/src/components/TimeOff/TimeOffFlow/index.ts b/src/components/TimeOff/TimeOffFlow/index.ts
deleted file mode 100644
index db1e7c9b2..000000000
--- a/src/components/TimeOff/TimeOffFlow/index.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-export { TimeOffFlow } from './TimeOffFlow'
-export type { TimeOffFlowProps } from './TimeOffFlowComponents'
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
deleted file mode 100644
index a4268c98c..000000000
--- a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.test.ts
+++ /dev/null
@@ -1,562 +0,0 @@
-import { describe, expect, it } from 'vitest'
-import { createMachine, interpret, type SendFunction } from 'robot3'
-import { timeOffMachine } from './timeOffStateMachine'
-import type { TimeOffFlowContextInterface } from './TimeOffFlowComponents'
-import { componentEvents } from '@/shared/constants'
-
-type TimeOffState =
- | 'policyList'
- | 'policyTypeSelector'
- | 'policyDetailsForm'
- | 'policySettings'
- | 'addEmployeesToPolicy'
- | 'viewPolicyDetails'
- | 'viewPolicyEmployees'
- | 'holidaySelectionForm'
- | 'addEmployeesHoliday'
- | 'viewHolidayEmployees'
- | 'viewHolidaySchedule'
- | 'final'
-
-function createTestMachine(initialState: TimeOffState = 'policyList') {
- return createMachine(
- initialState,
- timeOffMachine,
- (initialContext: TimeOffFlowContextInterface) => ({
- ...initialContext,
- component: () => null,
- companyId: 'company-123',
- }),
- )
-}
-
-function createService(initialState: TimeOffState = 'policyList') {
- const machine = createTestMachine(initialState)
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- return interpret(machine, () => {}, {} as any)
-}
-
-function send(service: ReturnType, type: string, payload?: unknown) {
- ;(service.send as SendFunction)({ type, payload })
-}
-
-function toPolicyTypeSelector(service: ReturnType) {
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- expect(service.machine.current).toBe('policyTypeSelector')
-}
-
-function toPolicyDetailsForm(service: ReturnType) {
- toPolicyTypeSelector(service)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
- expect(service.machine.current).toBe('policyDetailsForm')
-}
-
-function toPolicySettings(service: ReturnType) {
- toPolicyDetailsForm(service)
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
- expect(service.machine.current).toBe('policySettings')
-}
-
-function toAddEmployeesToPolicy(service: ReturnType) {
- toPolicySettings(service)
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
- expect(service.machine.current).toBe('addEmployeesToPolicy')
-}
-
-function toHolidaySelectionForm(service: ReturnType) {
- toPolicyTypeSelector(service)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
- expect(service.machine.current).toBe('holidaySelectionForm')
-}
-
-function toAddEmployeesHoliday(service: ReturnType) {
- toHolidaySelectionForm(service)
- send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
- expect(service.machine.current).toBe('addEmployeesHoliday')
-}
-
-describe('timeOffStateMachine', () => {
- describe('policyList state', () => {
- it('starts in policyList by default', () => {
- const service = createService()
- expect(service.machine.current).toBe('policyList')
- })
-
- it('transitions to policyTypeSelector on TIME_OFF_CREATE_POLICY', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to viewPolicyDetails on TIME_OFF_VIEW_POLICY with sick/vacation type', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-456',
- policyType: 'vacation',
- })
-
- expect(service.machine.current).toBe('viewPolicyDetails')
- expect(service.context.policyId).toBe('policy-456')
- expect(service.context.policyType).toBe('vacation')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to viewHolidayEmployees on TIME_OFF_VIEW_POLICY with holiday type', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-789',
- policyType: 'holiday',
- })
-
- expect(service.machine.current).toBe('viewHolidayEmployees')
- expect(service.context.policyId).toBe('policy-789')
- expect(service.context.policyType).toBe('holiday')
- expect(service.context.alerts).toBeUndefined()
- })
- })
-
- describe('policyTypeSelector state', () => {
- it('transitions to policyDetailsForm on sick type selection', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
-
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.policyType).toBe('sick')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to policyDetailsForm on vacation type selection', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
-
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.policyType).toBe('vacation')
- })
-
- it('transitions to holidaySelectionForm on holiday type selection', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
-
- expect(service.machine.current).toBe('holidaySelectionForm')
- expect(service.context.policyType).toBe('holiday')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions to policyList on CANCEL', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
- })
-
- describe('sick/vacation creation flow', () => {
- it('transitions policyDetailsForm -> policySettings on POLICY_DETAILS_DONE', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
-
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.policyId).toBe('policy-123')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions policySettings -> addEmployeesToPolicy on POLICY_SETTINGS_DONE', () => {
- const service = createService()
- toPolicySettings(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
-
- expect(service.machine.current).toBe('addEmployeesToPolicy')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions addEmployeesToPolicy -> viewPolicyDetails on ADD_EMPLOYEES_DONE', () => {
- const service = createService()
- toAddEmployeesToPolicy(service)
-
- send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
-
- expect(service.machine.current).toBe('viewPolicyDetails')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('supports full happy path: policyList -> viewPolicyDetails', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- expect(service.machine.current).toBe('policyTypeSelector')
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.policyType).toBe('vacation')
-
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-123' })
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.policyId).toBe('policy-123')
-
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_DONE)
- expect(service.machine.current).toBe('addEmployeesToPolicy')
-
- send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE)
- expect(service.machine.current).toBe('viewPolicyDetails')
- expect(service.context.policyId).toBe('policy-123')
- })
- })
-
- describe('holiday creation flow', () => {
- it('transitions holidaySelectionForm -> addEmployeesHoliday on HOLIDAY_SELECTION_DONE', () => {
- const service = createService()
- toHolidaySelectionForm(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
-
- expect(service.machine.current).toBe('addEmployeesHoliday')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('transitions addEmployeesHoliday -> viewHolidayEmployees on HOLIDAY_ADD_EMPLOYEES_DONE', () => {
- const service = createService()
- toAddEmployeesHoliday(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
-
- expect(service.machine.current).toBe('viewHolidayEmployees')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('supports full happy path: policyList -> viewHolidayEmployees', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'holiday' })
- expect(service.machine.current).toBe('holidaySelectionForm')
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE)
- expect(service.machine.current).toBe('addEmployeesHoliday')
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE)
- expect(service.machine.current).toBe('viewHolidayEmployees')
- })
- })
-
- describe('tab switching', () => {
- it('switches from viewPolicyDetails to viewPolicyEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
- expect(service.machine.current).toBe('viewPolicyDetails')
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
-
- expect(service.machine.current).toBe('viewPolicyEmployees')
- })
-
- it('switches from viewPolicyEmployees to viewPolicyDetails', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
- expect(service.machine.current).toBe('viewPolicyEmployees')
-
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_DETAILS)
-
- expect(service.machine.current).toBe('viewPolicyDetails')
- })
-
- it('switches from viewHolidayEmployees to viewHolidaySchedule', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
- expect(service.machine.current).toBe('viewHolidayEmployees')
-
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
-
- expect(service.machine.current).toBe('viewHolidaySchedule')
- })
-
- it('switches from viewHolidaySchedule to viewHolidayEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
- expect(service.machine.current).toBe('viewHolidaySchedule')
-
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES)
-
- expect(service.machine.current).toBe('viewHolidayEmployees')
- })
- })
-
- describe('back to list', () => {
- it('returns to policyList from viewPolicyDetails', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('returns to policyList from viewPolicyEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'vacation',
- })
- send(service, componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES)
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('returns to policyList from viewHolidayEmployees', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('returns to policyList from viewHolidaySchedule', () => {
- const service = createService()
- send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
- policyId: 'policy-123',
- policyType: 'holiday',
- })
- send(service, componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE)
-
- send(service, componentEvents.TIME_OFF_BACK_TO_LIST)
-
- expect(service.machine.current).toBe('policyList')
- })
- })
-
- describe('cancel transitions', () => {
- it('cancels from policyTypeSelector to policyList', () => {
- const service = createService()
- toPolicyTypeSelector(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('cancels from policyDetailsForm to policyList', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('cancels from policySettings to policyList', () => {
- const service = createService()
- toPolicySettings(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('cancels from addEmployeesToPolicy to policyList', () => {
- const service = createService()
- toAddEmployeesToPolicy(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('cancels from holidaySelectionForm to policyList', () => {
- const service = createService()
- toHolidaySelectionForm(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
-
- it('cancels from addEmployeesHoliday to policyList', () => {
- const service = createService()
- toAddEmployeesHoliday(service)
-
- send(service, componentEvents.CANCEL)
-
- expect(service.machine.current).toBe('policyList')
- })
- })
-
- describe('error transitions', () => {
- it('policyDetailsForm error transitions to policyTypeSelector with alert', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create policy' },
- })
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to create policy' }])
- })
-
- it('policyDetailsForm error without alert payload clears alerts', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {})
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('policySettings error transitions to policyDetailsForm with alert', () => {
- const service = createService()
- toPolicySettings(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR, {
- alert: { type: 'error', title: 'Failed to update settings' },
- })
-
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.alerts).toEqual([
- { type: 'error', title: 'Failed to update settings' },
- ])
- })
-
- it('addEmployeesToPolicy error transitions to policySettings with alert', () => {
- const service = createService()
- toAddEmployeesToPolicy(service)
-
- send(service, componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees' },
- })
-
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.alerts).toEqual([{ type: 'error', title: 'Failed to add employees' }])
- })
-
- it('holidaySelectionForm error transitions to policyTypeSelector with alert', () => {
- const service = createService()
- toHolidaySelectionForm(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Failed to create holiday policy' },
- })
-
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toEqual([
- { type: 'error', title: 'Failed to create holiday policy' },
- ])
- })
-
- it('addEmployeesHoliday error transitions to holidaySelectionForm with alert', () => {
- const service = createService()
- toAddEmployeesHoliday(service)
-
- send(service, componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR, {
- alert: { type: 'error', title: 'Failed to add employees to holiday' },
- })
-
- expect(service.machine.current).toBe('holidaySelectionForm')
- expect(service.context.alerts).toEqual([
- { type: 'error', title: 'Failed to add employees to holiday' },
- ])
- })
- })
-
- describe('alert lifecycle', () => {
- it('forward transition after error clears alerts', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Create failed' },
- })
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toEqual([{ type: 'error', title: 'Create failed' }])
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'sick' })
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('cancel clears alerts', () => {
- const service = createService()
- toPolicyDetailsForm(service)
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Create failed' },
- })
- expect(service.context.alerts).toBeDefined()
-
- send(service, componentEvents.CANCEL)
- expect(service.machine.current).toBe('policyList')
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('create policy clears alerts from policyList', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- expect(service.context.alerts).toBeUndefined()
- })
-
- it('error then retry full flow preserves correct state', () => {
- const service = createService()
-
- send(service, componentEvents.TIME_OFF_CREATE_POLICY)
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
-
- send(service, componentEvents.TIME_OFF_POLICY_CREATE_ERROR, {
- alert: { type: 'error', title: 'Create failed' },
- })
- expect(service.machine.current).toBe('policyTypeSelector')
- expect(service.context.alerts).toHaveLength(1)
-
- send(service, componentEvents.TIME_OFF_POLICY_TYPE_SELECTED, { policyType: 'vacation' })
- expect(service.machine.current).toBe('policyDetailsForm')
- expect(service.context.alerts).toBeUndefined()
-
- send(service, componentEvents.TIME_OFF_POLICY_DETAILS_DONE, { policyId: 'policy-456' })
- expect(service.machine.current).toBe('policySettings')
- expect(service.context.policyId).toBe('policy-456')
- expect(service.context.alerts).toBeUndefined()
- })
- })
-})
diff --git a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts b/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
deleted file mode 100644
index af33c841d..000000000
--- a/src/components/TimeOff/TimeOffFlow/timeOffStateMachine.ts
+++ /dev/null
@@ -1,359 +0,0 @@
-import { transition, reduce, state, guard } from 'robot3'
-import {
- PolicyListContextual,
- PolicyTypeSelectorContextual,
- PolicyDetailsFormContextual,
- PolicySettingsContextual,
- AddEmployeesToPolicyContextual,
- ViewPolicyDetailsContextual,
- ViewPolicyEmployeesContextual,
- HolidaySelectionFormContextual,
- AddEmployeesHolidayContextual,
- ViewHolidayEmployeesContextual,
- ViewHolidayScheduleContextual,
- type TimeOffFlowContextInterface,
- type TimeOffFlowAlert,
-} from './TimeOffFlowComponents'
-import { componentEvents } from '@/shared/constants'
-import type { MachineTransition } from '@/types/Helpers'
-
-type PolicyTypePayload = { policyType: 'sick' | 'vacation' | 'holiday' }
-type PolicyCreatedPayload = { policyId: string }
-type ErrorPayload = { alert?: TimeOffFlowAlert }
-type ViewPolicyPayload = { policyId: string; policyType: 'sick' | 'vacation' | 'holiday' }
-
-function isSickOrVacation(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
- return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
-}
-
-function isHoliday(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
- return ev.payload.policyType === 'holiday'
-}
-
-function isSickOrVacationView(
- _ctx: TimeOffFlowContextInterface,
- ev: { payload: ViewPolicyPayload },
-) {
- return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
-}
-
-function isHolidayView(_ctx: TimeOffFlowContextInterface, ev: { payload: ViewPolicyPayload }) {
- return ev.payload.policyType === 'holiday'
-}
-
-const cancelToPolicyList = transition(
- componentEvents.CANCEL,
- 'policyList',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyListContextual,
- alerts: undefined,
- }),
- ),
-)
-
-const backToListTransition = transition(
- componentEvents.TIME_OFF_BACK_TO_LIST,
- 'policyList',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyListContextual,
- alerts: undefined,
- }),
- ),
-)
-
-export const timeOffMachine = {
- policyList: state(
- transition(
- componentEvents.TIME_OFF_CREATE_POLICY,
- 'policyTypeSelector',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyTypeSelectorContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY,
- 'viewPolicyDetails',
- guard(isSickOrVacationView),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ViewPolicyPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyDetailsContextual,
- policyId: ev.payload.policyId,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY,
- 'viewHolidayEmployees',
- guard(isHolidayView),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ViewPolicyPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayEmployeesContextual,
- policyId: ev.payload.policyId,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- ),
-
- policyTypeSelector: state(
- transition(
- componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
- 'policyDetailsForm',
- guard(isSickOrVacation),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: PolicyTypePayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyDetailsFormContextual,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_POLICY_TYPE_SELECTED,
- 'holidaySelectionForm',
- guard(isHoliday),
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: PolicyTypePayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: HolidaySelectionFormContextual,
- policyType: ev.payload.policyType,
- alerts: undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- policyDetailsForm: state(
- transition(
- componentEvents.TIME_OFF_POLICY_DETAILS_DONE,
- 'policySettings',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: PolicyCreatedPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicySettingsContextual,
- policyId: ev.payload.policyId,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_POLICY_CREATE_ERROR,
- 'policyTypeSelector',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyTypeSelectorContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- policySettings: state(
- transition(
- componentEvents.TIME_OFF_POLICY_SETTINGS_DONE,
- 'addEmployeesToPolicy',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: AddEmployeesToPolicyContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_POLICY_SETTINGS_ERROR,
- 'policyDetailsForm',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyDetailsFormContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- addEmployeesToPolicy: state(
- transition(
- componentEvents.TIME_OFF_ADD_EMPLOYEES_DONE,
- 'viewPolicyDetails',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyDetailsContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_ADD_EMPLOYEES_ERROR,
- 'policySettings',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicySettingsContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- viewPolicyDetails: state(
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES,
- 'viewPolicyEmployees',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyEmployeesContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- viewPolicyEmployees: state(
- transition(
- componentEvents.TIME_OFF_VIEW_POLICY_DETAILS,
- 'viewPolicyDetails',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewPolicyDetailsContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- holidaySelectionForm: state(
- transition(
- componentEvents.TIME_OFF_HOLIDAY_SELECTION_DONE,
- 'addEmployeesHoliday',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: AddEmployeesHolidayContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_HOLIDAY_CREATE_ERROR,
- 'policyTypeSelector',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: PolicyTypeSelectorContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- addEmployeesHoliday: state(
- transition(
- componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_DONE,
- 'viewHolidayEmployees',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayEmployeesContextual,
- alerts: undefined,
- }),
- ),
- ),
- transition(
- componentEvents.TIME_OFF_HOLIDAY_ADD_EMPLOYEES_ERROR,
- 'holidaySelectionForm',
- reduce(
- (
- ctx: TimeOffFlowContextInterface,
- ev: { payload: ErrorPayload },
- ): TimeOffFlowContextInterface => ({
- ...ctx,
- component: HolidaySelectionFormContextual,
- alerts: ev.payload.alert ? [ev.payload.alert] : undefined,
- }),
- ),
- ),
- cancelToPolicyList,
- ),
-
- viewHolidayEmployees: state(
- transition(
- componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE,
- 'viewHolidaySchedule',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayScheduleContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- viewHolidaySchedule: state(
- transition(
- componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES,
- 'viewHolidayEmployees',
- reduce(
- (ctx: TimeOffFlowContextInterface): TimeOffFlowContextInterface => ({
- ...ctx,
- component: ViewHolidayEmployeesContextual,
- }),
- ),
- ),
- backToListTransition,
- ),
-
- final: state(),
-}
diff --git a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx b/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
deleted file mode 100644
index 65d6fc30b..000000000
--- a/src/components/TimeOff/ViewHolidayEmployees/ViewHolidayEmployees.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewHolidayEmployeesProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function ViewHolidayEmployees(props: ViewHolidayEmployeesProps) {
- return (
-
-
-
View Holiday Employees (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_SCHEDULE); }}>
- Holiday Schedule Tab
-
-
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx b/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
deleted file mode 100644
index 6bf6eea02..000000000
--- a/src/components/TimeOff/ViewHolidaySchedule/ViewHolidaySchedule.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewHolidayScheduleProps extends BaseComponentInterface {
- companyId: string
-}
-
-export function ViewHolidaySchedule(props: ViewHolidayScheduleProps) {
- return (
-
-
-
View Holiday Schedule (companyId: {props.companyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_HOLIDAY_EMPLOYEES); }}>
- View Employees Tab
-
-
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx b/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
deleted file mode 100644
index 4a7e3d43c..000000000
--- a/src/components/TimeOff/ViewPolicyDetails/ViewPolicyDetails.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewPolicyDetailsProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function ViewPolicyDetails(props: ViewPolicyDetailsProps) {
- return (
-
-
-
View Policy Details (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_EMPLOYEES); }}>
- View Employees Tab
-
-
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx b/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
deleted file mode 100644
index 4c92bb122..000000000
--- a/src/components/TimeOff/ViewPolicyEmployees/ViewPolicyEmployees.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { BaseComponent, type BaseComponentInterface } from '@/components/Base'
-import { componentEvents } from '@/shared/constants'
-
-export interface ViewPolicyEmployeesProps extends BaseComponentInterface {
- policyId: string
-}
-
-export function ViewPolicyEmployees(props: ViewPolicyEmployeesProps) {
- return (
-
-
-
View Policy Employees (policyId: {props.policyId})
-
{ props.onEvent(componentEvents.TIME_OFF_VIEW_POLICY_DETAILS); }}>
- Policy Details Tab
-
-
{ props.onEvent(componentEvents.TIME_OFF_BACK_TO_LIST); }}>
- Back to List
-
-
-
- )
-}
diff --git a/src/components/TimeOff/index.ts b/src/components/TimeOff/index.ts
deleted file mode 100644
index eac24ad97..000000000
--- a/src/components/TimeOff/index.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export { PolicyList } from './PolicyList/PolicyList'
-export type { PolicyListProps } from './PolicyList/PolicyList'
-export { PolicyTypeSelector } from './PolicyTypeSelector/PolicyTypeSelector'
-export type { PolicyTypeSelectorProps } from './PolicyTypeSelector/PolicyTypeSelector'
-export { PolicyDetailsForm } from './PolicyDetailsForm/PolicyDetailsForm'
-export type { PolicyDetailsFormProps } from './PolicyDetailsForm/PolicyDetailsForm'
-export { PolicySettings } from './PolicySettings/PolicySettings'
-export type { PolicySettingsProps } from './PolicySettings/PolicySettings'
-export { AddEmployeesToPolicy } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
-export type { AddEmployeesToPolicyProps } from './AddEmployeesToPolicy/AddEmployeesToPolicy'
-export { ViewPolicyDetails } from './ViewPolicyDetails/ViewPolicyDetails'
-export type { ViewPolicyDetailsProps } from './ViewPolicyDetails/ViewPolicyDetails'
-export { ViewPolicyEmployees } from './ViewPolicyEmployees/ViewPolicyEmployees'
-export type { ViewPolicyEmployeesProps } from './ViewPolicyEmployees/ViewPolicyEmployees'
-export { HolidaySelectionForm } from './HolidaySelectionForm/HolidaySelectionForm'
-export type { HolidaySelectionFormProps } from './HolidaySelectionForm/HolidaySelectionForm'
-export { AddEmployeesHoliday } from './AddEmployeesHoliday/AddEmployeesHoliday'
-export type { AddEmployeesHolidayProps } from './AddEmployeesHoliday/AddEmployeesHoliday'
-export { ViewHolidayEmployees } from './ViewHolidayEmployees/ViewHolidayEmployees'
-export type { ViewHolidayEmployeesProps } from './ViewHolidayEmployees/ViewHolidayEmployees'
-export { ViewHolidaySchedule } from './ViewHolidaySchedule/ViewHolidaySchedule'
-export type { ViewHolidayScheduleProps } from './ViewHolidaySchedule/ViewHolidaySchedule'
-export { TimeOffFlow } from './TimeOffFlow/TimeOffFlow'
-export type { TimeOffFlowProps } from './TimeOffFlow/TimeOffFlowComponents'
diff --git a/src/components/TimeOff/PolicyList/PolicyListPresentation.stories.tsx b/src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.stories.tsx
similarity index 100%
rename from src/components/TimeOff/PolicyList/PolicyListPresentation.stories.tsx
rename to src/components/UNSTABLE_TimeOff/PolicyList/PolicyListPresentation.stories.tsx