Initial commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useAnalytics } from './useAnalytics'
|
||||
import * as api from '@/services/api'
|
||||
|
||||
vi.mock('@/services/api')
|
||||
|
||||
describe('useAnalytics', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('initializes with null data', () => {
|
||||
const { result } = renderHook(() => useAnalytics(null))
|
||||
expect(result.current.data).toBeNull()
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('fetches analytics data on mount', async () => {
|
||||
const mockData = {
|
||||
workspace_id: 'ws1',
|
||||
total_days: 25,
|
||||
days_used: 10,
|
||||
days_left: 15,
|
||||
projected_left_at_age_4: 50,
|
||||
}
|
||||
|
||||
vi.mocked(api.getWorkspaceOverview).mockResolvedValue(mockData as any)
|
||||
|
||||
const { result } = renderHook(() => useAnalytics('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(api.getWorkspaceOverview).toHaveBeenCalledWith('ws1')
|
||||
})
|
||||
|
||||
it('handles fetch errors', async () => {
|
||||
vi.mocked(api.getWorkspaceOverview).mockRejectedValue(new Error('API error'))
|
||||
|
||||
const { result } = renderHook(() => useAnalytics('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(result.current.error).toContain('API error')
|
||||
})
|
||||
|
||||
it('does not fetch when workspaceId is null', () => {
|
||||
vi.mocked(api.getWorkspaceOverview).mockResolvedValue({} as any)
|
||||
|
||||
renderHook(() => useAnalytics(null))
|
||||
|
||||
expect(api.getWorkspaceOverview).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('provides refresh function', async () => {
|
||||
vi.mocked(api.getWorkspaceOverview).mockResolvedValue({} as any)
|
||||
|
||||
const { result } = renderHook(() => useAnalytics('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.refresh()
|
||||
})
|
||||
|
||||
expect(api.getWorkspaceOverview).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import type { WorkspaceOverview } from '@/types'
|
||||
|
||||
interface UseAnalyticsResult {
|
||||
data: WorkspaceOverview | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refresh: () => Promise<void>
|
||||
}
|
||||
|
||||
export function useAnalytics(workspaceId: string | null): UseAnalyticsResult {
|
||||
const [data, setData] = useState<WorkspaceOverview | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!workspaceId) return
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await api.getWorkspaceOverview(workspaceId)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch analytics')
|
||||
setData(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [workspaceId])
|
||||
|
||||
useEffect(() => {
|
||||
fetch()
|
||||
}, [fetch])
|
||||
|
||||
return { data, isLoading, error, refresh: fetch }
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useBreakPeriods } from './useBreakPeriods'
|
||||
import * as api from '@/services/api'
|
||||
|
||||
vi.mock('@/services/api')
|
||||
|
||||
describe('useBreakPeriods', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('initializes with null data', () => {
|
||||
const { result } = renderHook(() => useBreakPeriods(null))
|
||||
expect(result.current.data).toBeNull()
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
expect(result.current.error).toBeNull()
|
||||
})
|
||||
|
||||
it('fetches break periods on mount', async () => {
|
||||
const mockData = [
|
||||
{
|
||||
id: '1',
|
||||
workspace_id: 'ws1',
|
||||
name: 'Summer',
|
||||
start_date: '2026-06-01',
|
||||
end_date: '2026-08-31',
|
||||
target_weeks: 8,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
},
|
||||
]
|
||||
|
||||
vi.mocked(api.getBreakPeriods).mockResolvedValue(mockData as any)
|
||||
|
||||
const { result } = renderHook(() => useBreakPeriods('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(api.getBreakPeriods).toHaveBeenCalledWith('ws1')
|
||||
})
|
||||
|
||||
it('handles errors during fetch', async () => {
|
||||
vi.mocked(api.getBreakPeriods).mockRejectedValue(new Error('Network error'))
|
||||
|
||||
const { result } = renderHook(() => useBreakPeriods('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(result.current.error).toContain('Network error')
|
||||
expect(result.current.data).toBeNull()
|
||||
})
|
||||
|
||||
it('adds break period and refreshes', async () => {
|
||||
const mockAdded = {
|
||||
id: '2',
|
||||
workspace_id: 'ws1',
|
||||
name: 'Winter',
|
||||
start_date: '2026-12-01',
|
||||
end_date: '2026-12-31',
|
||||
target_weeks: 2,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
vi.mocked(api.createBreakPeriod).mockResolvedValue(mockAdded as any)
|
||||
vi.mocked(api.getBreakPeriods).mockResolvedValue([mockAdded] as any)
|
||||
|
||||
const { result } = renderHook(() => useBreakPeriods('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.add('2026-12-01', '2026-12-31', 'Winter', 2)
|
||||
})
|
||||
|
||||
expect(api.createBreakPeriod).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('removes break period and refreshes', async () => {
|
||||
vi.mocked(api.deleteBreakPeriod).mockResolvedValue(undefined)
|
||||
vi.mocked(api.getBreakPeriods).mockResolvedValue([])
|
||||
|
||||
const { result } = renderHook(() => useBreakPeriods('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.remove('bp1')
|
||||
})
|
||||
|
||||
expect(api.deleteBreakPeriod).toHaveBeenCalledWith('bp1')
|
||||
})
|
||||
|
||||
it('does not fetch when workspaceId is null', () => {
|
||||
vi.mocked(api.getBreakPeriods).mockResolvedValue([])
|
||||
|
||||
renderHook(() => useBreakPeriods(null))
|
||||
|
||||
expect(api.getBreakPeriods).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,67 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import type { BreakPeriod } from '@/types'
|
||||
|
||||
interface UseBreakPeriodsResult {
|
||||
data: BreakPeriod[] | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refresh: () => Promise<void>
|
||||
add: (
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
name: string,
|
||||
targetWeeks: number
|
||||
) => Promise<BreakPeriod>
|
||||
remove: (id: string) => Promise<void>
|
||||
}
|
||||
|
||||
export function useBreakPeriods(workspaceId: string | null): UseBreakPeriodsResult {
|
||||
const [data, setData] = useState<BreakPeriod[] | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!workspaceId) return
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await api.getBreakPeriods(workspaceId)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch break periods')
|
||||
setData(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [workspaceId])
|
||||
|
||||
const add = useCallback(
|
||||
async (startDate: string, endDate: string, name: string, targetWeeks: number) => {
|
||||
if (!workspaceId) throw new Error('Workspace not selected')
|
||||
const result = await api.createBreakPeriod(workspaceId, {
|
||||
name,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
target_weeks: targetWeeks,
|
||||
})
|
||||
await fetch()
|
||||
return result
|
||||
},
|
||||
[workspaceId, fetch]
|
||||
)
|
||||
|
||||
const remove = useCallback(
|
||||
async (id: string) => {
|
||||
await api.deleteBreakPeriod(id)
|
||||
await fetch()
|
||||
},
|
||||
[fetch]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
fetch()
|
||||
}, [fetch])
|
||||
|
||||
return { data, isLoading, error, refresh: fetch, add, remove }
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useOptimizationTips } from './useOptimizationTips'
|
||||
import * as api from '@/services/api'
|
||||
|
||||
vi.mock('@/services/api')
|
||||
|
||||
describe('useOptimizationTips', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('initializes with null data', () => {
|
||||
const { result } = renderHook(() => useOptimizationTips(null))
|
||||
expect(result.current.data).toBeNull()
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('fetches tips on mount', async () => {
|
||||
const mockTips = [
|
||||
{
|
||||
id: 'tip1',
|
||||
severity: 'info',
|
||||
message_key: 'wrap_red_days',
|
||||
data: {},
|
||||
},
|
||||
]
|
||||
|
||||
vi.mocked(api.getTips).mockResolvedValue(mockTips as any)
|
||||
|
||||
const { result } = renderHook(() => useOptimizationTips('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(api.getTips).toHaveBeenCalledWith('ws1')
|
||||
})
|
||||
|
||||
it('handles fetch errors', async () => {
|
||||
vi.mocked(api.getTips).mockRejectedValue(new Error('API error'))
|
||||
|
||||
const { result } = renderHook(() => useOptimizationTips('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(result.current.error).toContain('API error')
|
||||
})
|
||||
|
||||
it('dismisses tips locally', async () => {
|
||||
const mockTips = [
|
||||
{ id: 'tip1', severity: 'info', message_key: 'msg1', data: {} },
|
||||
{ id: 'tip2', severity: 'warning', message_key: 'msg2', data: {} },
|
||||
]
|
||||
|
||||
vi.mocked(api.getTips).mockResolvedValue(mockTips as any)
|
||||
|
||||
const { result } = renderHook(() => useOptimizationTips('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(result.current.data).toHaveLength(2)
|
||||
|
||||
act(() => {
|
||||
result.current.dismiss('tip1')
|
||||
})
|
||||
|
||||
expect(result.current.data).toHaveLength(1)
|
||||
expect(result.current.data?.[0].id).toBe('tip2')
|
||||
})
|
||||
|
||||
it('provides refresh function', async () => {
|
||||
vi.mocked(api.getTips).mockResolvedValue([])
|
||||
|
||||
const { result } = renderHook(() => useOptimizationTips('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.refresh()
|
||||
})
|
||||
|
||||
expect(api.getTips).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('does not fetch when workspaceId is null', () => {
|
||||
vi.mocked(api.getTips).mockResolvedValue([])
|
||||
|
||||
renderHook(() => useOptimizationTips(null))
|
||||
|
||||
expect(api.getTips).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import type { Tip } from '@/types'
|
||||
|
||||
interface UseOptimizationTipsResult {
|
||||
data: Tip[] | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refresh: () => Promise<void>
|
||||
dismiss: (tipId: string) => void
|
||||
}
|
||||
|
||||
export function useOptimizationTips(workspaceId: string | null): UseOptimizationTipsResult {
|
||||
const [data, setData] = useState<Tip[] | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [dismissed, setDismissed] = useState<Set<string>>(new Set())
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!workspaceId) return
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await api.getTips(workspaceId)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch tips')
|
||||
setData(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [workspaceId])
|
||||
|
||||
const dismiss = useCallback((tipId: string) => {
|
||||
setDismissed((prev) => new Set([...prev, tipId]))
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetch()
|
||||
}, [fetch])
|
||||
|
||||
const filteredData = data?.filter((tip) => !dismissed.has(tip.id)) ?? null
|
||||
|
||||
return { data: filteredData, isLoading, error, refresh: fetch, dismiss }
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { useParentalLeave } from './useParentalLeave'
|
||||
import * as api from '@/services/api'
|
||||
|
||||
vi.mock('@/services/api')
|
||||
|
||||
describe('useParentalLeave', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('initializes with null data', () => {
|
||||
const { result } = renderHook(() => useParentalLeave(null))
|
||||
expect(result.current.data).toBeNull()
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('fetches parental leave data on mount', async () => {
|
||||
const mockData = {
|
||||
workspace_id: 'ws1',
|
||||
days_used: 20,
|
||||
days_available: 300,
|
||||
}
|
||||
|
||||
vi.mocked(api.getParentalLeave).mockResolvedValue(mockData as any)
|
||||
|
||||
const { result } = renderHook(() => useParentalLeave('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(api.getParentalLeave).toHaveBeenCalledWith('ws1')
|
||||
})
|
||||
|
||||
it('handles fetch errors', async () => {
|
||||
vi.mocked(api.getParentalLeave).mockRejectedValue(new Error('Fetch failed'))
|
||||
|
||||
const { result } = renderHook(() => useParentalLeave('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(result.current.error).toContain('Fetch failed')
|
||||
})
|
||||
|
||||
it('does not fetch when workspaceId is null', () => {
|
||||
vi.mocked(api.getParentalLeave).mockResolvedValue({} as any)
|
||||
|
||||
renderHook(() => useParentalLeave(null))
|
||||
|
||||
expect(api.getParentalLeave).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('provides refresh function', async () => {
|
||||
vi.mocked(api.getParentalLeave).mockResolvedValue({} as any)
|
||||
|
||||
const { result } = renderHook(() => useParentalLeave('ws1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.refresh()
|
||||
})
|
||||
|
||||
expect(api.getParentalLeave).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import type { ParentalLeaveFetch } from '@/types'
|
||||
|
||||
interface UseParentalLeaveResult {
|
||||
data: ParentalLeaveFetch | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refresh: () => Promise<void>
|
||||
}
|
||||
|
||||
export function useParentalLeave(workspaceId: string | null): UseParentalLeaveResult {
|
||||
const [data, setData] = useState<ParentalLeaveFetch | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!workspaceId) return
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await api.getParentalLeave(workspaceId)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch parental leave data')
|
||||
setData(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [workspaceId])
|
||||
|
||||
useEffect(() => {
|
||||
fetch()
|
||||
}, [fetch])
|
||||
|
||||
return { data, isLoading, error, refresh: fetch }
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { usePlannedDays } from './usePlannedDays'
|
||||
import * as api from '@/services/api'
|
||||
|
||||
vi.mock('@/services/api')
|
||||
|
||||
describe('usePlannedDays', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('initializes with null data', () => {
|
||||
const { result } = renderHook(() => usePlannedDays(null))
|
||||
expect(result.current.data).toBeNull()
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
|
||||
it('fetches planned days when both ids provided', async () => {
|
||||
const mockData = [
|
||||
{
|
||||
id: '1',
|
||||
workspace_id: 'ws1',
|
||||
child_id: 'child1',
|
||||
date: '2026-06-15',
|
||||
day_type: 'vacation',
|
||||
is_dubbeldag: false,
|
||||
created_at: new Date().toISOString(),
|
||||
},
|
||||
]
|
||||
|
||||
vi.mocked(api.getPlannedDays).mockResolvedValue(mockData as any)
|
||||
|
||||
const { result } = renderHook(() => usePlannedDays('ws1', 'child1'))
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 0))
|
||||
})
|
||||
|
||||
expect(api.getPlannedDays).toHaveBeenCalledWith('ws1', 'child1')
|
||||
})
|
||||
|
||||
it('does not fetch without childId', () => {
|
||||
vi.mocked(api.getPlannedDays).mockResolvedValue([])
|
||||
|
||||
renderHook(() => usePlannedDays('ws1'))
|
||||
|
||||
expect(api.getPlannedDays).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('adds planned day and refreshes', async () => {
|
||||
const mockAdded = {
|
||||
id: '2',
|
||||
workspace_id: 'ws1',
|
||||
child_id: 'child1',
|
||||
date: '2026-06-16',
|
||||
day_type: 'vacation',
|
||||
is_dubbeldag: false,
|
||||
created_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
vi.mocked(api.createPlannedDay).mockResolvedValue(mockAdded as any)
|
||||
vi.mocked(api.getPlannedDays).mockResolvedValue([mockAdded])
|
||||
|
||||
const { result } = renderHook(() => usePlannedDays('ws1', 'child1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.add('2026-06-16', 'child1', 'vacation')
|
||||
})
|
||||
|
||||
expect(api.createPlannedDay).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('removes planned day and refreshes', async () => {
|
||||
vi.mocked(api.deletePlannedDay).mockResolvedValue(undefined)
|
||||
vi.mocked(api.getPlannedDays).mockResolvedValue([])
|
||||
|
||||
const { result } = renderHook(() => usePlannedDays('ws1', 'child1'))
|
||||
|
||||
await act(async () => {
|
||||
await result.current.remove('pd1')
|
||||
})
|
||||
|
||||
expect(api.deletePlannedDay).toHaveBeenCalledWith('pd1')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import type { PlannedLeaveDay } from '@/types'
|
||||
|
||||
interface UsePlannedDaysResult {
|
||||
data: PlannedLeaveDay[] | null
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
refresh: () => Promise<void>
|
||||
add: (date: string, childId: string, leaveType: string) => Promise<PlannedLeaveDay>
|
||||
remove: (id: string) => Promise<void>
|
||||
}
|
||||
|
||||
export function usePlannedDays(workspaceId: string | null, childId?: string): UsePlannedDaysResult {
|
||||
const [data, setData] = useState<PlannedLeaveDay[] | null>(null)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!workspaceId || !childId) return
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const response = await api.getPlannedDays(workspaceId, childId)
|
||||
setData(response)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to fetch planned days')
|
||||
setData(null)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [workspaceId, childId])
|
||||
|
||||
const add = useCallback(
|
||||
async (date: string, childId: string, leaveType: string) => {
|
||||
if (!workspaceId) throw new Error('Workspace not selected')
|
||||
const result = await api.createPlannedDay(workspaceId, childId, {
|
||||
date,
|
||||
day_type: leaveType,
|
||||
})
|
||||
await fetch()
|
||||
return result
|
||||
},
|
||||
[workspaceId, fetch]
|
||||
)
|
||||
|
||||
const remove = useCallback(
|
||||
async (id: string) => {
|
||||
await api.deletePlannedDay(id)
|
||||
await fetch()
|
||||
},
|
||||
[fetch]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
fetch()
|
||||
}, [fetch])
|
||||
|
||||
return { data, isLoading, error, refresh: fetch, add, remove }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* REFERENCE HOOK — mirrors the VacationDays backend slice on the frontend.
|
||||
*
|
||||
* Pattern to copy for other hooks (usePlannedDays, useParentalLeave, etc.):
|
||||
* 1. Call the typed api.* function.
|
||||
* 2. Track { data, isLoading, error }.
|
||||
* 3. Expose a mutation (upsert/create/delete) that calls the API and refreshes.
|
||||
* 4. Write a test in tests/unit/hooks/ using vi.mock('@/services/api').
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import * as api from '@/services/api'
|
||||
import type { VacationDays, VacationDaysCreateRequest } from '@/types'
|
||||
|
||||
interface UseVacationDaysResult {
|
||||
data: VacationDays[]
|
||||
isLoading: boolean
|
||||
error: string | null
|
||||
upsert: (req: VacationDaysCreateRequest) => Promise<void>
|
||||
}
|
||||
|
||||
export function useVacationDays(workspaceId: string): UseVacationDaysResult {
|
||||
const [data, setData] = useState<VacationDays[]>([])
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const fetch = useCallback(async () => {
|
||||
if (!workspaceId) return
|
||||
setIsLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const result = await api.getVacationDaysByWorkspace(workspaceId)
|
||||
setData(result)
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : 'Failed to load vacation days')
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [workspaceId])
|
||||
|
||||
useEffect(() => {
|
||||
void fetch()
|
||||
}, [fetch])
|
||||
|
||||
const upsert = useCallback(
|
||||
async (req: VacationDaysCreateRequest) => {
|
||||
await api.upsertVacationDays(req)
|
||||
await fetch()
|
||||
},
|
||||
[fetch],
|
||||
)
|
||||
|
||||
return { data, isLoading, error, upsert }
|
||||
}
|
||||
Reference in New Issue
Block a user