Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions src/components/UNSTABLE_TimeOff/PolicyList/PolicyList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
invalidateAllHolidayPayPoliciesGet,
} from '@gusto/embedded-api/react-query/holidayPayPoliciesGet'
import { useHolidayPayPoliciesDeleteMutation } from '@gusto/embedded-api/react-query/holidayPayPoliciesDelete'
import { GustoEmbeddedError } from '@gusto/embedded-api/models/errors/gustoembeddederror'
import type { TimeOffPolicy } from '@gusto/embedded-api/models/components/timeoffpolicy'
import { PolicyListPresentation } from './PolicyListPresentation'
import type { PolicyListItem } from './PolicyListTypes'
Expand Down Expand Up @@ -56,18 +55,13 @@ function Root({ companyId, onEvent }: PolicyListProps) {
})
const timeOffPolicies = (policiesData.timeOffPolicies ?? []).filter(policy => policy.isActive)

// Holiday pay policy is auxiliary to the main time-off list; never crash the
// boundary on its failure. composeErrorHandler below surfaces the error as
// an inline alert via BaseLayout when it isn't an expected 204/404.
const holidayQuery = useHolidayPayPoliciesGet(
{ companyUuid: companyId },
{
throwOnError: (error: Error) => {
if (error instanceof GustoEmbeddedError) {
const status = error.httpMeta.response.status
if (status === 204 || status === 404) {
return false
}
}
return true
},
throwOnError: () => false,
},
)
const holidayPayPolicy = holidayQuery.data?.holidayPayPolicy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from '@/components/Common'
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
import { useI18n } from '@/i18n'
import { isEditableTimeOffPolicyType } from '@/components/UNSTABLE_TimeOff/TimeOffFlow/timeOffPolicyTypes'

export function PolicyListPresentation({
policies,
Expand Down Expand Up @@ -67,10 +68,35 @@ export function PolicyListPresentation({
],
itemMenu: (policy: PolicyListItem) => {
const isDeleting = isDeletingPolicyId === policy.uuid
const isEditable = isEditableTimeOffPolicyType(policy.policyType)

const menuItems = isEditable
? [
{
label: t('actions.editPolicy'),
onClick: () => {
onEditPolicy(policy)
},
},
{
label: t('actions.deletePolicy'),
onClick: () => {
handleOpenDeleteDialog(policy)
},
},
]
: [
{
label: t('actions.viewPolicy'),
onClick: () => {
onEditPolicy(policy)
},
},
]

return (
<div className={styles.actionsCell}>
{!policy.isComplete && (
{isEditable && !policy.isComplete && (
<Button
variant="secondary"
onClick={() => {
Expand All @@ -80,24 +106,7 @@ export function PolicyListPresentation({
{t('finishSetupCta')}
</Button>
)}
<HamburgerMenu
isLoading={isDeleting}
menuLabel={t('tableLabel')}
items={[
{
label: t('actions.editPolicy'),
onClick: () => {
onEditPolicy(policy)
},
},
{
label: t('actions.deletePolicy'),
onClick: () => {
handleOpenDeleteDialog(policy)
},
},
]}
/>
<HamburgerMenu isLoading={isDeleting} menuLabel={t('tableLabel')} items={menuItems} />
</div>
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export function SelectPolicyTypeContextual() {
const { onEvent, companyId, policyType, alerts } = useFlow<TimeOffFlowContextInterface>()
const { Alert } = useComponentContext()

const selectorDefault =
policyType === 'sick' || policyType === 'vacation' || policyType === 'holiday'
? policyType
: undefined

return (
<Flex flexDirection="column" gap={8}>
{alerts?.map((alert, index) => (
Expand All @@ -63,7 +68,7 @@ export function SelectPolicyTypeContextual() {
<PolicyTypeSelector
onEvent={onEvent}
companyId={ensureRequired(companyId)}
defaultPolicyType={policyType}
defaultPolicyType={selectorDefault}
/>
</Flex>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,17 @@ import type { PolicyType } from '@gusto/embedded-api/models/components/timeoffpo
// Holiday is a distinct concept routed through @gusto/embedded-api's
// holidayPayPolicies* hooks against a different endpoint family.
export type CreatableTimeOffPolicyType = Extract<PolicyType, 'sick' | 'vacation'>
export type TimeOffPolicyType = CreatableTimeOffPolicyType | 'holiday'
export type TimeOffPolicyType = PolicyType | 'holiday'

export const EDITABLE_TIME_OFF_POLICY_TYPES = ['sick', 'vacation', 'holiday'] as const

export type EditableTimeOffPolicyType = (typeof EDITABLE_TIME_OFF_POLICY_TYPES)[number]

export function isEditableTimeOffPolicyType(
policyType: string | null | undefined,
): policyType is EditableTimeOffPolicyType {
return EDITABLE_TIME_OFF_POLICY_TYPES.includes(policyType as EditableTimeOffPolicyType)
}

export function assertCreatablePolicyType(
policyType: TimeOffPolicyType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ describe('timeOffStateMachine', () => {
expect(service.context.policyType).toBe('holiday')
expect(service.context.alerts).toBeUndefined()
})

it.each(['custom', 'bereavement', 'parental_leave', 'jury_duty', 'volunteer'])(
'transitions to viewTimeOffPolicyDetail on TIME_OFF_VIEW_POLICY with non-holiday type %s',
policyType => {
const service = createService()

send(service, componentEvents.TIME_OFF_VIEW_POLICY, {
policyId: `policy-${policyType}`,
policyType,
})

expect(service.machine.current).toBe('viewTimeOffPolicyDetail')
expect(service.context.policyId).toBe(`policy-${policyType}`)
expect(service.context.policyType).toBe(policyType)
},
)
})

describe('policyTypeSelector state', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import {
type TimeOffFlowContextInterface,
type TimeOffFlowAlert,
} from './TimeOffFlowComponents'
import type { TimeOffPolicyType } from './timeOffPolicyTypes'
import { componentEvents } from '@/shared/constants'
import type { MachineTransition } from '@/types/Helpers'

type PolicyTypePayload = { policyType: 'sick' | 'vacation' | 'holiday' }
type PolicyCreatedPayload = { policyId: string; accrualMethod?: string }
type ErrorPayload = { alert?: TimeOffFlowAlert }
type ViewPolicyPayload = { policyId: string; policyType: 'sick' | 'vacation' | 'holiday' }
type ViewPolicyPayload = { policyId: string; policyType: TimeOffPolicyType }
type PolicyIdPayload = { policyId: string }

function isSickOrVacation(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypePayload }) {
Expand All @@ -30,11 +31,8 @@ function isHoliday(_ctx: TimeOffFlowContextInterface, ev: { payload: PolicyTypeP
return ev.payload.policyType === 'holiday'
}

function isSickOrVacationView(
_ctx: TimeOffFlowContextInterface,
ev: { payload: ViewPolicyPayload },
) {
return ev.payload.policyType === 'sick' || ev.payload.policyType === 'vacation'
function isNonHolidayView(_ctx: TimeOffFlowContextInterface, ev: { payload: ViewPolicyPayload }) {
return ev.payload.policyType !== 'holiday'
}

function isHolidayView(_ctx: TimeOffFlowContextInterface, ev: { payload: ViewPolicyPayload }) {
Expand Down Expand Up @@ -88,7 +86,7 @@ export const timeOffMachine = {
transition(
componentEvents.TIME_OFF_VIEW_POLICY,
'viewTimeOffPolicyDetail',
guard(isSickOrVacationView),
guard(isNonHolidayView),
reduce(
(
ctx: TimeOffFlowContextInterface,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useBase } from '@/components/Base/useBase'
import { componentEvents } from '@/shared/constants'
import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
import { useI18n } from '@/i18n'
import { isEditableTimeOffPolicyType } from '@/components/UNSTABLE_TimeOff/TimeOffFlow/timeOffPolicyTypes'
import EditIcon from '@/assets/icons/edit-02.svg?react'
import TrashCanSvg from '@/assets/icons/trashcan.svg?react'
import PlusCircleIcon from '@/assets/icons/plus-circle.svg?react'
Expand Down Expand Up @@ -143,6 +144,8 @@ function Root({ policyId }: TimeOffPolicyDetailProps) {
const policy = policyResponse.timeOffPolicy
if (!policy) throw new Error('Unexpected response: missing timeOffPolicy')

const isEditable = isEditableTimeOffPolicyType(policy.policyType)

const { data: employeesData } = useEmployeesListSuspense({
companyId: policy.companyUuid,
terminated: false,
Expand Down Expand Up @@ -265,8 +268,9 @@ function Root({ policyId }: TimeOffPolicyDetailProps) {
[selectedEmployeeUuids],
)

const actions = useMemo(
() => [
const actions = useMemo(() => {
if (!isEditable) return undefined
return [
<Button
key="add"
variant="secondary"
Expand All @@ -287,9 +291,8 @@ function Root({ policyId }: TimeOffPolicyDetailProps) {
>
{t('editPolicyCta')}
</Button>,
],
[Button, onEvent, policyId, t],
)
]
}, [Button, onEvent, policyId, t, isEditable])

const itemMenu = useCallback(
(employee: TimeOffPolicyDetailEmployee) => {
Expand Down Expand Up @@ -345,9 +348,11 @@ function Root({ policyId }: TimeOffPolicyDetailProps) {
: {
policyDetails,
policySettings: policySettings!,
onChangeSettings: () => {
onEvent(componentEvents.TIME_OFF_CHANGE_SETTINGS, { policyId })
},
onChangeSettings: isEditable
? () => {
onEvent(componentEvents.TIME_OFF_CHANGE_SETTINGS, { policyId })
}
: undefined,
}

return (
Expand All @@ -370,12 +375,16 @@ function Root({ policyId }: TimeOffPolicyDetailProps) {
onSearchClear: () => {
setSearchValue('')
},
itemMenu,
selectionMode: 'multiple',
onSelect: handleSelect,
onSelectAll: handleSelectAll,
getIsItemSelected,
footer,
...(isEditable
? {
itemMenu,
selectionMode: 'multiple',
onSelect: handleSelect,
onSelectAll: handleSelectAll,
getIsItemSelected,
footer,
}
: {}),
}}
removeDialog={{
isOpen: removeTarget !== null,
Expand Down
1 change: 1 addition & 0 deletions src/i18n/en/Company.TimeOff.TimeOffPolicies.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"tableLabel": "Time off policies",
"actions": {
"editPolicy": "Edit policy",
"viewPolicy": "View policy",
"deletePolicy": "Delete policy"
},
"finishSetupCta": "Finish setup",
Expand Down
1 change: 1 addition & 0 deletions src/types/i18next.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ export interface CompanyTimeOffTimeOffPolicies{
"tableLabel":string;
"actions":{
"editPolicy":string;
"viewPolicy":string;
"deletePolicy":string;
};
"finishSetupCta":string;
Expand Down
Loading