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})

- - +
) 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})

- - +
) 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})

- + ) 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})

- 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})

- - +
) 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})

- +
) 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})

- -
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})

- -
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})

- -
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})

- -
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 && ( + + )} + { onEditPolicy(policy); }, + }, + { + label: t('actions.deletePolicy'), + onClick: () => { handleOpenDeleteDialog(policy); }, + }, + ]} + /> +
+ ) + }, + emptyState: () => ( + + + + + + ), + }) + + return ( + + {deleteSuccessAlert && ( + + )} + + + {t('pageTitle')} + {policies.length > 0 && ( + + )} + + + + + + {t('deletePolicyDialog.description', { + name: deletePolicyDialogState.policy?.name ?? '', + })} + + + ) +} 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 && ( - )} @@ -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})

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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}) -

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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 && ( - - )} - { - onEditPolicy(policy) - }, - }, - { - label: t('actions.deletePolicy'), - onClick: () => { - handleOpenDeleteDialog(policy) - }, - }, - ]} - /> -
- ) - }, - emptyState: () => ( - - - - - - ), - }) - - return ( - - {deleteSuccessAlert && ( - - )} - - - {t('pageTitle')} - {policies.length > 0 && ( - - )} - - - - - - {t('deletePolicyDialog.description', { - name: deletePolicyDialogState.policy?.name ?? '', - })} - - - ) -} 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})

- - - -
-
- ) -} 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})

- - - - -
-
- ) -} 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})

- - -
-
- ) -} 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})

- - -
-
- ) -} 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})

- - -
-
- ) -} 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})

- - -
-
- ) -} 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 && ( + + )} + { + onEditPolicy(policy) + }, + }, + { + label: t('actions.deletePolicy'), + onClick: () => { + handleOpenDeleteDialog(policy) + }, + }, + ]} + /> +
+ ) + }, + emptyState: () => ( + + + + + + ), + }) + + return ( + + {deleteSuccessAlert && ( + + )} + + + {t('pageTitle')} + {policies.length > 0 && ( + + )} + + + + + + {t('deletePolicyDialog.description', { + name: deletePolicyDialogState.policy?.name ?? '', + })} + + + ) +} 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})

+ + + +
+
+ ) +} 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 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 (
- {t('icons.magnifyingGlass')} - {title && ( - - {title} - + {icon || ( + {t('icons.magnifyingGlass')} )} - {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})

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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}) -

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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})

- - - -
-
- ) -} 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})

- - - - -
-
- ) -} 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})

- - -
-
- ) -} 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})

- - -
-
- ) -} 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})

- - -
-
- ) -} 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})

- - -
-
- ) -} 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