diff --git a/src/components/Common/DetailViewLayout/DetailViewLayout.stories.tsx b/src/components/Common/DetailViewLayout/DetailViewLayout.stories.tsx
index 362b11803..96b11187e 100644
--- a/src/components/Common/DetailViewLayout/DetailViewLayout.stories.tsx
+++ b/src/components/Common/DetailViewLayout/DetailViewLayout.stories.tsx
@@ -42,7 +42,7 @@ function EmployeesTabContent() {
)
}
-function PolicyActions() {
+function usePolicyActions() {
const Components = useComponentContext()
return (
@@ -59,6 +59,7 @@ function PolicyActions() {
export const Default = () => {
const [selectedTabId, setSelectedTabId] = useState('details')
+ const actions = usePolicyActions()
const tabs = [
{ id: 'details', label: 'Details', content: },
@@ -71,7 +72,7 @@ export const Default = () => {
subtitle="Paid time off policy"
onBack={() => {}}
backLabel="Time off policies"
- actions={}
+ actions={actions}
tabs={tabs}
selectedTabId={selectedTabId}
onTabChange={setSelectedTabId}
@@ -81,6 +82,7 @@ export const Default = () => {
export const WithoutBackButton = () => {
const [selectedTabId, setSelectedTabId] = useState('details')
+ const actions = usePolicyActions()
const tabs = [
{ id: 'details', label: 'Details', content: },
@@ -91,7 +93,7 @@ export const WithoutBackButton = () => {
}
+ actions={actions}
tabs={tabs}
selectedTabId={selectedTabId}
onTabChange={setSelectedTabId}
diff --git a/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetail.stories.tsx b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetail.stories.tsx
new file mode 100644
index 000000000..9335a00d6
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetail.stories.tsx
@@ -0,0 +1,323 @@
+import { Suspense, useState } from 'react'
+import { fn } from 'storybook/test'
+import type { HolidayItem } from '../HolidaySelectionForm/HolidaySelectionFormTypes'
+import { HolidayPolicyDetailPresentation } from './HolidayPolicyDetailPresentation'
+import type { HolidayPolicyDetailEmployee } from './HolidayPolicyDetailTypes'
+import { HamburgerMenu } from '@/components/Common/HamburgerMenu'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+import PlusCircleIcon from '@/assets/icons/plus-circle.svg?react'
+import EditIcon from '@/assets/icons/edit-02.svg?react'
+import TrashCanSvg from '@/assets/icons/trashcan.svg?react'
+
+export default {
+ title: 'Domain/TimeOff/HolidayPolicyDetail',
+ decorators: [
+ (Story: React.ComponentType) => (
+ Loading translations...}>
+
+
+ ),
+ ],
+}
+
+const mockHolidays: HolidayItem[] = [
+ {
+ uuid: 'newYearsDay',
+ name: "New Year's Day",
+ observedDate: 'January 1',
+ nextObservation: 'January 1, 2027',
+ },
+ {
+ uuid: 'mlkDay',
+ name: 'Martin Luther King, Jr. Day',
+ observedDate: 'Third Monday in January',
+ nextObservation: 'January 18, 2027',
+ },
+ {
+ uuid: 'presidentsDay',
+ name: "Presidents' Day",
+ observedDate: 'Third Monday in February',
+ nextObservation: 'February 15, 2027',
+ },
+ {
+ uuid: 'memorialDay',
+ name: 'Memorial Day',
+ observedDate: 'Last Monday in May',
+ nextObservation: 'May 31, 2027',
+ },
+ {
+ uuid: 'juneteenth',
+ name: 'Juneteenth',
+ observedDate: 'June 19',
+ nextObservation: 'June 19, 2026',
+ },
+ {
+ uuid: 'independenceDay',
+ name: 'Independence Day',
+ observedDate: 'July 4',
+ nextObservation: 'July 4, 2026',
+ },
+ {
+ uuid: 'laborDay',
+ name: 'Labor Day',
+ observedDate: 'First Monday in September',
+ nextObservation: 'September 7, 2026',
+ },
+ {
+ uuid: 'columbusDay',
+ name: "Columbus Day (Indigenous Peoples' Day)",
+ observedDate: 'Second Monday in October',
+ nextObservation: 'October 12, 2026',
+ },
+ {
+ uuid: 'veteransDay',
+ name: 'Veterans Day',
+ observedDate: 'November 11',
+ nextObservation: 'November 11, 2026',
+ },
+ {
+ uuid: 'thanksgiving',
+ name: 'Thanksgiving',
+ observedDate: 'Fourth Thursday in November',
+ nextObservation: 'November 26, 2026',
+ },
+ {
+ uuid: 'christmasDay',
+ name: 'Christmas Day',
+ observedDate: 'December 25',
+ nextObservation: 'December 25, 2026',
+ },
+]
+
+const mockEmployees: HolidayPolicyDetailEmployee[] = [
+ { uuid: '1', firstName: 'Alejandro', lastName: 'Kuhic', jobTitle: 'Marketing Director' },
+ { uuid: '2', firstName: 'Alexander', lastName: 'Hamilton', jobTitle: 'Engineer' },
+ { uuid: '3', firstName: 'Arthur', lastName: 'Schopenhauer', jobTitle: 'Engineer' },
+ { uuid: '4', firstName: 'Friedrich', lastName: 'Nietzsche', jobTitle: 'Engineer' },
+ { uuid: '5', firstName: 'Hannah', lastName: 'Arendt', jobTitle: 'Account Manager' },
+ { uuid: '6', firstName: 'Immanuel', lastName: 'Kant', jobTitle: 'Client Support Manager' },
+]
+
+const onBack = fn().mockName('onBack')
+const onTabChange = fn().mockName('onTabChange')
+const onDismissAlert = fn().mockName('onDismissAlert')
+const onRemoveConfirm = fn().mockName('onRemoveConfirm')
+const onRemoveClose = fn().mockName('onRemoveClose')
+
+function useSearchState() {
+ const [searchValue, setSearchValue] = useState('')
+ return {
+ searchValue,
+ onSearchChange: setSearchValue,
+ onSearchClear: () => {
+ setSearchValue('')
+ },
+ }
+}
+
+function usePolicyActions() {
+ const { Button } = useComponentContext()
+
+ return [
+ } onClick={fn()}>
+ Add employees
+ ,
+ } onClick={fn()}>
+ Edit policy
+ ,
+ ]
+}
+
+const closedRemoveDialog = {
+ isOpen: false,
+ employeeName: '',
+ onConfirm: onRemoveConfirm,
+ onClose: onRemoveClose,
+ isPending: false,
+}
+
+export const HolidaysTab = () => {
+ const [selectedTabId, setSelectedTabId] = useState('holidays')
+ const search = useSearchState()
+ const actions = usePolicyActions()
+
+ return (
+ {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={closedRemoveDialog}
+ />
+ )
+}
+
+export const EmployeesTab = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+ const actions = usePolicyActions()
+
+ return (
+ {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={closedRemoveDialog}
+ />
+ )
+}
+
+export const WithSuccessAlert = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+ const actions = usePolicyActions()
+
+ return (
+ {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={closedRemoveDialog}
+ successAlert="Alejandro Kuhic has been removed from the policy."
+ onDismissAlert={onDismissAlert}
+ />
+ )
+}
+
+export const RemoveDialogOpen = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+ const actions = usePolicyActions()
+
+ return (
+ {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={{
+ isOpen: true,
+ employeeName: 'Alejandro Kuhic',
+ onConfirm: onRemoveConfirm,
+ onClose: onRemoveClose,
+ isPending: false,
+ }}
+ />
+ )
+}
+
+export const EmptyEmployees = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+ const actions = usePolicyActions()
+
+ return (
+ {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: [],
+ ...search,
+ }}
+ removeDialog={closedRemoveDialog}
+ />
+ )
+}
diff --git a/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetailPresentation.tsx b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetailPresentation.tsx
new file mode 100644
index 000000000..6240c39ed
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetailPresentation.tsx
@@ -0,0 +1,85 @@
+import { useMemo } from 'react'
+import { useTranslation } from 'react-i18next'
+import type { HolidayItem } from '../HolidaySelectionForm/HolidaySelectionFormTypes'
+import { PolicyDetailLayout } from '../shared/PolicyDetailLayout'
+import type { HolidayPolicyDetailPresentationProps } from './HolidayPolicyDetailTypes'
+import { DataView, useDataView } from '@/components/Common'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+import { useI18n } from '@/i18n'
+
+const HOLIDAYS_TAB_ID = 'holidays'
+
+export function HolidayPolicyDetailPresentation({
+ title,
+ subtitle,
+ onBack,
+ backLabel,
+ actions,
+ holidays,
+ selectedTabId,
+ onTabChange,
+ employees,
+ removeDialog,
+ successAlert,
+ onDismissAlert,
+}: HolidayPolicyDetailPresentationProps) {
+ useI18n('Company.TimeOff.HolidayPolicy')
+ const { t } = useTranslation('Company.TimeOff.HolidayPolicy')
+
+ const holidaysTabContent =
+
+ return (
+
+ )
+}
+
+function HolidaysTab({ holidays }: { holidays: HolidayItem[] }) {
+ useI18n('Company.TimeOff.HolidayPolicy')
+ const { t } = useTranslation('Company.TimeOff.HolidayPolicy')
+ const { Text } = useComponentContext()
+
+ const columns = useMemo(
+ () => [
+ {
+ key: 'name' as keyof HolidayItem,
+ title: t('tableHeaders.holidayName'),
+ render: (item: HolidayItem) => {item.name},
+ },
+ {
+ key: 'observedDate' as keyof HolidayItem,
+ title: t('tableHeaders.observedDate'),
+ render: (item: HolidayItem) => {item.observedDate},
+ },
+ {
+ key: 'nextObservation' as keyof HolidayItem,
+ title: t('tableHeaders.nextObservation'),
+ render: (item: HolidayItem) => {item.nextObservation},
+ },
+ ],
+ [t, Text],
+ )
+
+ const dataViewProps = useDataView({
+ data: holidays,
+ columns,
+ })
+
+ return
+}
diff --git a/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetailTypes.ts b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetailTypes.ts
new file mode 100644
index 000000000..53dd11228
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/HolidayPolicyDetailTypes.ts
@@ -0,0 +1,41 @@
+import type { ReactNode } from 'react'
+import type { HolidayItem } from '../HolidaySelectionForm/HolidaySelectionFormTypes'
+import type {
+ EmployeeTableItem,
+ EmployeeTableProps,
+} from '../shared/EmployeeTable/EmployeeTableTypes'
+import type { RemoveDialogState } from '../shared/PolicyDetailLayout/PolicyDetailLayoutTypes'
+
+export interface HolidayPolicyDetailEmployee extends EmployeeTableItem {
+ uuid: string
+}
+
+export interface HolidayPolicyDetailPresentationProps {
+ title: string
+ subtitle?: string
+ onBack: () => void
+ backLabel: string
+ actions?: ReactNode[]
+
+ holidays: HolidayItem[]
+
+ selectedTabId: string
+ onTabChange: (id: string) => void
+
+ employees: Pick<
+ EmployeeTableProps,
+ | 'data'
+ | 'searchValue'
+ | 'onSearchChange'
+ | 'onSearchClear'
+ | 'searchPlaceholder'
+ | 'itemMenu'
+ | 'pagination'
+ | 'isFetching'
+ | 'emptyState'
+ >
+
+ removeDialog: RemoveDialogState
+ successAlert?: string
+ onDismissAlert?: () => void
+}
diff --git a/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/index.ts b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/index.ts
new file mode 100644
index 000000000..8570410ad
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/HolidayPolicyDetail/index.ts
@@ -0,0 +1,5 @@
+export { HolidayPolicyDetailPresentation } from './HolidayPolicyDetailPresentation'
+export type {
+ HolidayPolicyDetailPresentationProps,
+ HolidayPolicyDetailEmployee,
+} from './HolidayPolicyDetailTypes'
diff --git a/src/components/UNSTABLE_TimeOff/index.ts b/src/components/UNSTABLE_TimeOff/index.ts
index d4c62394c..a9456015d 100644
--- a/src/components/UNSTABLE_TimeOff/index.ts
+++ b/src/components/UNSTABLE_TimeOff/index.ts
@@ -20,5 +20,10 @@ export { ViewHolidayEmployees } from './ViewHolidayEmployees/ViewHolidayEmployee
export type { ViewHolidayEmployeesProps } from './ViewHolidayEmployees/ViewHolidayEmployees'
export { ViewHolidaySchedule } from './ViewHolidaySchedule/ViewHolidaySchedule'
export type { ViewHolidayScheduleProps } from './ViewHolidaySchedule/ViewHolidaySchedule'
+export { HolidayPolicyDetailPresentation as HolidayPolicyDetail } from './HolidayPolicyDetail'
+export type {
+ HolidayPolicyDetailPresentationProps as HolidayPolicyDetailProps,
+ HolidayPolicyDetailEmployee,
+} from './HolidayPolicyDetail'
export { TimeOffFlow } from './TimeOffFlow/TimeOffFlow'
export type { TimeOffFlowProps } from './TimeOffFlow/TimeOffFlowComponents'
diff --git a/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayout.stories.tsx b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayout.stories.tsx
new file mode 100644
index 000000000..1f2e465db
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayout.stories.tsx
@@ -0,0 +1,233 @@
+import { Suspense, useState } from 'react'
+import { fn } from 'storybook/test'
+import type { EmployeeTableItem } from '../EmployeeTable/EmployeeTableTypes'
+import { PolicyDetailLayout } from './PolicyDetailLayout'
+import { HamburgerMenu } from '@/components/Common/HamburgerMenu'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+import TrashCanSvg from '@/assets/icons/trashcan.svg?react'
+
+export default {
+ title: 'Domain/TimeOff/PolicyDetailLayout',
+ decorators: [
+ (Story: React.ComponentType) => (
+ Loading translations...}>
+
+
+ ),
+ ],
+}
+
+interface StoryEmployee extends EmployeeTableItem {
+ uuid: string
+}
+
+const mockEmployees: StoryEmployee[] = [
+ { uuid: '1', firstName: 'Alejandro', lastName: 'Kuhic', jobTitle: 'Marketing Director' },
+ { uuid: '2', firstName: 'Alexander', lastName: 'Hamilton', jobTitle: 'Engineer' },
+ { uuid: '3', firstName: 'Arthur', lastName: 'Schopenhauer', jobTitle: 'Engineer' },
+ { uuid: '4', firstName: 'Friedrich', lastName: 'Nietzsche', jobTitle: 'Engineer' },
+ { uuid: '5', firstName: 'Hannah', lastName: 'Arendt', jobTitle: 'Account Manager' },
+]
+
+const onBack = fn().mockName('onBack')
+const onTabChange = fn().mockName('onTabChange')
+
+function useSearchState() {
+ const [searchValue, setSearchValue] = useState('')
+ return {
+ searchValue,
+ onSearchChange: setSearchValue,
+ onSearchClear: () => {
+ setSearchValue('')
+ },
+ }
+}
+
+function PlaceholderTabContent() {
+ const { Text } = useComponentContext()
+ return (
+
+ This is the domain-specific first tab content (e.g. holidays table, policy settings).
+
+ )
+}
+
+const closedRemoveDialog = {
+ isOpen: false,
+ employeeName: '',
+ onConfirm: fn().mockName('onRemoveConfirm'),
+ onClose: fn().mockName('onRemoveClose'),
+ isPending: false,
+}
+
+export const FirstTabSelected = () => {
+ const [selectedTabId, setSelectedTabId] = useState('details')
+ const search = useSearchState()
+
+ return (
+
+ title="Company PTO"
+ subtitle="Paid time off policy"
+ onBack={onBack}
+ backLabel="Back to policies"
+ firstTab={{
+ id: 'details',
+ label: 'Details',
+ content: ,
+ }}
+ selectedTabId={selectedTabId}
+ onTabChange={id => {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={closedRemoveDialog}
+ />
+ )
+}
+
+export const EmployeesTabSelected = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+
+ return (
+
+ title="Company PTO"
+ subtitle="Paid time off policy"
+ onBack={onBack}
+ backLabel="Back to policies"
+ firstTab={{
+ id: 'details',
+ label: 'Details',
+ content: ,
+ }}
+ selectedTabId={selectedTabId}
+ onTabChange={id => {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={closedRemoveDialog}
+ />
+ )
+}
+
+export const WithSuccessAlert = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+
+ return (
+
+ title="Company PTO"
+ subtitle="Paid time off policy"
+ onBack={onBack}
+ backLabel="Back to policies"
+ firstTab={{
+ id: 'details',
+ label: 'Details',
+ content: ,
+ }}
+ selectedTabId={selectedTabId}
+ onTabChange={id => {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={closedRemoveDialog}
+ successAlert="Friedrich Nietzsche has been removed from the policy."
+ onDismissAlert={fn().mockName('onDismissAlert')}
+ />
+ )
+}
+
+export const RemoveDialogOpen = () => {
+ const [selectedTabId, setSelectedTabId] = useState('employees')
+ const search = useSearchState()
+
+ return (
+
+ title="Company PTO"
+ subtitle="Paid time off policy"
+ onBack={onBack}
+ backLabel="Back to policies"
+ firstTab={{
+ id: 'details',
+ label: 'Details',
+ content: ,
+ }}
+ selectedTabId={selectedTabId}
+ onTabChange={id => {
+ setSelectedTabId(id)
+ onTabChange(id)
+ }}
+ employees={{
+ data: mockEmployees,
+ ...search,
+ itemMenu: employee => (
+ ,
+ onClick: fn().mockName(`remove-${employee.firstName}`),
+ },
+ ]}
+ triggerLabel={`Actions for ${employee.firstName} ${employee.lastName}`}
+ />
+ ),
+ }}
+ removeDialog={{
+ isOpen: true,
+ employeeName: 'Friedrich Nietzsche',
+ onConfirm: fn().mockName('onRemoveConfirm'),
+ onClose: fn().mockName('onRemoveClose'),
+ isPending: false,
+ }}
+ />
+ )
+}
diff --git a/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayout.tsx b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayout.tsx
new file mode 100644
index 000000000..9d1925d35
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayout.tsx
@@ -0,0 +1,84 @@
+import { useTranslation } from 'react-i18next'
+import type { EmployeeTableItem } from '../EmployeeTable/EmployeeTableTypes'
+import { EmployeeTable } from '../EmployeeTable/EmployeeTable'
+import type { PolicyDetailLayoutProps } from './PolicyDetailLayoutTypes'
+import { DetailViewLayout } from '@/components/Common'
+import { useComponentContext } from '@/contexts/ComponentAdapter/useComponentContext'
+import { useI18n } from '@/i18n'
+
+const EMPLOYEES_TAB_ID = 'employees'
+
+export function PolicyDetailLayout({
+ title,
+ subtitle,
+ onBack,
+ backLabel,
+ actions,
+ firstTab,
+ selectedTabId,
+ onTabChange,
+ employees,
+ removeDialog,
+ successAlert,
+ onDismissAlert,
+}: PolicyDetailLayoutProps) {
+ useI18n('Company.TimeOff.PolicyDetail')
+ const { t } = useTranslation('Company.TimeOff.PolicyDetail')
+ const { Alert, Dialog } = useComponentContext()
+
+ const tabs = [
+ {
+ id: firstTab.id,
+ label: firstTab.label,
+ content: firstTab.content,
+ },
+ {
+ id: EMPLOYEES_TAB_ID,
+ label: t('tabs.employees'),
+ content: (
+
+ data={employees.data}
+ searchValue={employees.searchValue}
+ onSearchChange={employees.onSearchChange}
+ onSearchClear={employees.onSearchClear}
+ searchPlaceholder={employees.searchPlaceholder}
+ itemMenu={employees.itemMenu}
+ pagination={employees.pagination}
+ isFetching={employees.isFetching}
+ emptyState={employees.emptyState}
+ additionalColumns={employees.additionalColumns}
+ />
+ ),
+ },
+ ]
+
+ return (
+ <>
+ {successAlert && }
+
+
+
+
+ >
+ )
+}
diff --git a/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayoutTypes.ts b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayoutTypes.ts
new file mode 100644
index 000000000..a5db78f1d
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/PolicyDetailLayoutTypes.ts
@@ -0,0 +1,44 @@
+import type { ReactNode } from 'react'
+import type { EmployeeTableItem, EmployeeTableProps } from '../EmployeeTable/EmployeeTableTypes'
+
+export interface RemoveDialogState {
+ isOpen: boolean
+ employeeName: string
+ onConfirm: () => void
+ onClose: () => void
+ isPending: boolean
+}
+
+export interface PolicyDetailLayoutProps {
+ title: string
+ subtitle?: string
+ onBack: () => void
+ backLabel: string
+ actions?: ReactNode[]
+
+ firstTab: {
+ id: string
+ label: string
+ content: ReactNode
+ }
+ selectedTabId: string
+ onTabChange: (id: string) => void
+
+ employees: Pick<
+ EmployeeTableProps,
+ | 'data'
+ | 'searchValue'
+ | 'onSearchChange'
+ | 'onSearchClear'
+ | 'searchPlaceholder'
+ | 'itemMenu'
+ | 'pagination'
+ | 'isFetching'
+ | 'emptyState'
+ | 'additionalColumns'
+ >
+
+ removeDialog: RemoveDialogState
+ successAlert?: string
+ onDismissAlert?: () => void
+}
diff --git a/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/index.ts b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/index.ts
new file mode 100644
index 000000000..751a6fb98
--- /dev/null
+++ b/src/components/UNSTABLE_TimeOff/shared/PolicyDetailLayout/index.ts
@@ -0,0 +1,2 @@
+export { PolicyDetailLayout } from './PolicyDetailLayout'
+export type { PolicyDetailLayoutProps, RemoveDialogState } from './PolicyDetailLayoutTypes'
diff --git a/src/i18n/en/Company.TimeOff.HolidayPolicy.json b/src/i18n/en/Company.TimeOff.HolidayPolicy.json
index 873461b79..02aab69c8 100644
--- a/src/i18n/en/Company.TimeOff.HolidayPolicy.json
+++ b/src/i18n/en/Company.TimeOff.HolidayPolicy.json
@@ -14,7 +14,12 @@
},
"show": {
"title": "Holiday pay policy",
- "holidaySchedule": "Holiday schedule"
+ "holidaySchedule": "Holiday schedule",
+ "addEmployeesCta": "Add employees",
+ "editPolicyCta": "Edit policy"
+ },
+ "tabs": {
+ "holidays": "Holidays"
},
"holidayScheduleTable": {
"title": "Holiday schedule",
diff --git a/src/i18n/en/Company.TimeOff.PolicyDetail.json b/src/i18n/en/Company.TimeOff.PolicyDetail.json
new file mode 100644
index 000000000..78bf6312b
--- /dev/null
+++ b/src/i18n/en/Company.TimeOff.PolicyDetail.json
@@ -0,0 +1,12 @@
+{
+ "tabs": {
+ "employees": "Employees"
+ },
+ "backLabel": "Back to policies",
+ "removeEmployeeDialog": {
+ "title": "Remove {{name}}",
+ "description": "Are you sure you want to remove {{name}} from this policy?",
+ "confirmCta": "Remove",
+ "cancelCta": "Cancel"
+ }
+}
diff --git a/src/types/i18next.d.ts b/src/types/i18next.d.ts
index 906fbcdc5..587e19a9d 100644
--- a/src/types/i18next.d.ts
+++ b/src/types/i18next.d.ts
@@ -489,6 +489,11 @@ export interface CompanyTimeOffHolidayPolicy{
"show":{
"title":string;
"holidaySchedule":string;
+"addEmployeesCta":string;
+"editPolicyCta":string;
+};
+"tabs":{
+"holidays":string;
};
"holidayScheduleTable":{
"title":string;
@@ -549,6 +554,18 @@ export interface CompanyTimeOffHolidayPolicy{
"employeesRemoved_other":string;
};
};
+export interface CompanyTimeOffPolicyDetail{
+"tabs":{
+"employees":string;
+};
+"backLabel":string;
+"removeEmployeeDialog":{
+"title":string;
+"description":string;
+"confirmCta":string;
+"cancelCta":string;
+};
+};
export interface CompanyTimeOffSelectEmployees{
"title":string;
"description":string;
@@ -3379,6 +3396,6 @@ export interface common{
interface CustomTypeOptions {
defaultNS: 'common';
- resources: { 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Company.TimeOff.CreateTimeOffPolicy': CompanyTimeOffCreateTimeOffPolicy, 'Company.TimeOff.EmployeeTable': CompanyTimeOffEmployeeTable, 'Company.TimeOff.HolidayPolicy': CompanyTimeOffHolidayPolicy, 'Company.TimeOff.SelectEmployees': CompanyTimeOffSelectEmployees, 'Company.TimeOff.SelectPolicyType': CompanyTimeOffSelectPolicyType, 'Company.TimeOff.TimeOffPolicies': CompanyTimeOffTimeOffPolicies, 'Company.TimeOff.TimeOffPolicyDetails': CompanyTimeOffTimeOffPolicyDetails, 'Company.TimeOff.TimeOffRequests': CompanyTimeOffTimeOffRequests, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Payments.CreatePayment': ContractorPaymentsCreatePayment, 'Contractor.Payments.PaymentHistory': ContractorPaymentsPaymentHistory, 'Contractor.Payments.PaymentStatement': ContractorPaymentsPaymentStatement, 'Contractor.Payments.PaymentSummary': ContractorPaymentsPaymentSummary, 'Contractor.Payments.PaymentsList': ContractorPaymentsPaymentsList, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Dashboard': EmployeeDashboard, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeDocuments': EmployeeEmployeeDocuments, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.EmploymentEligibility': EmployeeEmploymentEligibility, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress.Management': EmployeeHomeAddressManagement, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.I9SignatureForm': EmployeeI9SignatureForm, 'Employee.Landing': EmployeeLanding, 'Employee.ManagementEmployeeList': EmployeeManagementEmployeeList, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Employee.Terminations.TerminateEmployee': EmployeeTerminationsTerminateEmployee, 'Employee.Terminations.TerminationFlow': EmployeeTerminationsTerminationFlow, 'Employee.Terminations.TerminationSummary': EmployeeTerminationsTerminationSummary, 'Employee.WorkAddress.Management': EmployeeWorkAddressManagement, 'InformationRequests.InformationRequestForm': InformationRequestsInformationRequestForm, 'InformationRequests.InformationRequestList': InformationRequestsInformationRequestList, 'InformationRequests': InformationRequests, 'Payroll.Common': PayrollCommon, 'Payroll.ConfirmWireDetailsBanner': PayrollConfirmWireDetailsBanner, 'Payroll.ConfirmWireDetailsForm': PayrollConfirmWireDetailsForm, 'Payroll.Dismissal': PayrollDismissal, 'Payroll.EmployeeSelection': PayrollEmployeeSelection, 'Payroll.GrossUpModal': PayrollGrossUpModal, 'Payroll.OffCycle': PayrollOffCycle, 'Payroll.OffCycleCreation': PayrollOffCycleCreation, 'Payroll.OffCycleDeductionsSetting': PayrollOffCycleDeductionsSetting, 'Payroll.OffCyclePayPeriodDateForm': PayrollOffCyclePayPeriodDateForm, 'Payroll.OffCycleReasonSelection': PayrollOffCycleReasonSelection, 'Payroll.OffCycleTaxWithholding': PayrollOffCycleTaxWithholding, 'Payroll.PayrollBlocker': PayrollPayrollBlocker, 'Payroll.PayrollConfiguration': PayrollPayrollConfiguration, 'Payroll.PayrollEditEmployee': PayrollPayrollEditEmployee, 'Payroll.PayrollFlow': PayrollPayrollFlow, 'Payroll.PayrollHistory': PayrollPayrollHistory, 'Payroll.PayrollLanding': PayrollPayrollLanding, 'Payroll.PayrollList': PayrollPayrollList, 'Payroll.PayrollOverview': PayrollPayrollOverview, 'Payroll.PayrollReceipts': PayrollPayrollReceipts, 'Payroll.RecoveryCasesList': PayrollRecoveryCasesList, 'Payroll.RecoveryCasesResubmit': PayrollRecoveryCasesResubmit, 'Payroll.Transition': PayrollTransition, 'Payroll.TransitionCreation': PayrollTransitionCreation, 'Payroll.TransitionPayrollAlert': PayrollTransitionPayrollAlert, 'Payroll.WireInstructions': PayrollWireInstructions, 'common': common, }
+ resources: { 'Company.Addresses': CompanyAddresses, 'Company.AssignSignatory': CompanyAssignSignatory, 'Company.BankAccount': CompanyBankAccount, 'Company.DocumentList': CompanyDocumentList, 'Company.FederalTaxes': CompanyFederalTaxes, 'Company.Industry': CompanyIndustry, 'Company.Locations': CompanyLocations, 'Company.OnboardingOverview': CompanyOnboardingOverview, 'Company.PaySchedule': CompanyPaySchedule, 'Company.SignatureForm': CompanySignatureForm, 'Company.StateTaxes': CompanyStateTaxes, 'Company.TimeOff.CreateTimeOffPolicy': CompanyTimeOffCreateTimeOffPolicy, 'Company.TimeOff.EmployeeTable': CompanyTimeOffEmployeeTable, 'Company.TimeOff.HolidayPolicy': CompanyTimeOffHolidayPolicy, 'Company.TimeOff.PolicyDetail': CompanyTimeOffPolicyDetail, 'Company.TimeOff.SelectEmployees': CompanyTimeOffSelectEmployees, 'Company.TimeOff.SelectPolicyType': CompanyTimeOffSelectPolicyType, 'Company.TimeOff.TimeOffPolicies': CompanyTimeOffTimeOffPolicies, 'Company.TimeOff.TimeOffPolicyDetails': CompanyTimeOffTimeOffPolicyDetails, 'Company.TimeOff.TimeOffRequests': CompanyTimeOffTimeOffRequests, 'Contractor.Address': ContractorAddress, 'Contractor.ContractorList': ContractorContractorList, 'Contractor.NewHireReport': ContractorNewHireReport, 'Contractor.PaymentMethod': ContractorPaymentMethod, 'Contractor.Payments.CreatePayment': ContractorPaymentsCreatePayment, 'Contractor.Payments.PaymentHistory': ContractorPaymentsPaymentHistory, 'Contractor.Payments.PaymentStatement': ContractorPaymentsPaymentStatement, 'Contractor.Payments.PaymentSummary': ContractorPaymentsPaymentSummary, 'Contractor.Payments.PaymentsList': ContractorPaymentsPaymentsList, 'Contractor.Profile': ContractorProfile, 'Contractor.Submit': ContractorSubmit, 'Employee.BankAccount': EmployeeBankAccount, 'Employee.Compensation': EmployeeCompensation, 'Employee.Dashboard': EmployeeDashboard, 'Employee.Deductions': EmployeeDeductions, 'Employee.DocumentSigner': EmployeeDocumentSigner, 'Employee.EmployeeDocuments': EmployeeEmployeeDocuments, 'Employee.EmployeeList': EmployeeEmployeeList, 'Employee.EmploymentEligibility': EmployeeEmploymentEligibility, 'Employee.FederalTaxes': EmployeeFederalTaxes, 'Employee.HomeAddress.Management': EmployeeHomeAddressManagement, 'Employee.HomeAddress': EmployeeHomeAddress, 'Employee.I9SignatureForm': EmployeeI9SignatureForm, 'Employee.Landing': EmployeeLanding, 'Employee.ManagementEmployeeList': EmployeeManagementEmployeeList, 'Employee.OnboardingSummary': EmployeeOnboardingSummary, 'Employee.PaySchedules': EmployeePaySchedules, 'Employee.PaymentMethod': EmployeePaymentMethod, 'Employee.Profile': EmployeeProfile, 'Employee.SplitPaycheck': EmployeeSplitPaycheck, 'Employee.StateTaxes': EmployeeStateTaxes, 'Employee.Taxes': EmployeeTaxes, 'Employee.Terminations.TerminateEmployee': EmployeeTerminationsTerminateEmployee, 'Employee.Terminations.TerminationFlow': EmployeeTerminationsTerminationFlow, 'Employee.Terminations.TerminationSummary': EmployeeTerminationsTerminationSummary, 'Employee.WorkAddress.Management': EmployeeWorkAddressManagement, 'InformationRequests.InformationRequestForm': InformationRequestsInformationRequestForm, 'InformationRequests.InformationRequestList': InformationRequestsInformationRequestList, 'InformationRequests': InformationRequests, 'Payroll.Common': PayrollCommon, 'Payroll.ConfirmWireDetailsBanner': PayrollConfirmWireDetailsBanner, 'Payroll.ConfirmWireDetailsForm': PayrollConfirmWireDetailsForm, 'Payroll.Dismissal': PayrollDismissal, 'Payroll.EmployeeSelection': PayrollEmployeeSelection, 'Payroll.GrossUpModal': PayrollGrossUpModal, 'Payroll.OffCycle': PayrollOffCycle, 'Payroll.OffCycleCreation': PayrollOffCycleCreation, 'Payroll.OffCycleDeductionsSetting': PayrollOffCycleDeductionsSetting, 'Payroll.OffCyclePayPeriodDateForm': PayrollOffCyclePayPeriodDateForm, 'Payroll.OffCycleReasonSelection': PayrollOffCycleReasonSelection, 'Payroll.OffCycleTaxWithholding': PayrollOffCycleTaxWithholding, 'Payroll.PayrollBlocker': PayrollPayrollBlocker, 'Payroll.PayrollConfiguration': PayrollPayrollConfiguration, 'Payroll.PayrollEditEmployee': PayrollPayrollEditEmployee, 'Payroll.PayrollFlow': PayrollPayrollFlow, 'Payroll.PayrollHistory': PayrollPayrollHistory, 'Payroll.PayrollLanding': PayrollPayrollLanding, 'Payroll.PayrollList': PayrollPayrollList, 'Payroll.PayrollOverview': PayrollPayrollOverview, 'Payroll.PayrollReceipts': PayrollPayrollReceipts, 'Payroll.RecoveryCasesList': PayrollRecoveryCasesList, 'Payroll.RecoveryCasesResubmit': PayrollRecoveryCasesResubmit, 'Payroll.Transition': PayrollTransition, 'Payroll.TransitionCreation': PayrollTransitionCreation, 'Payroll.TransitionPayrollAlert': PayrollTransitionPayrollAlert, 'Payroll.WireInstructions': PayrollWireInstructions, 'common': common, }
};
}
\ No newline at end of file