diff --git a/backend/app/routers/planned_days.py b/backend/app/routers/planned_days.py index 194c932..5739c82 100644 --- a/backend/app/routers/planned_days.py +++ b/backend/app/routers/planned_days.py @@ -1,6 +1,5 @@ """HTTP routes for planned leave days (calendar planning).""" -from datetime import date from uuid import UUID from fastapi import APIRouter, HTTPException, Query, status @@ -9,6 +8,7 @@ from app.dependencies import CurrentUserDep, PlanningServiceDep from app.models.planned_leave import ( PlannedLeaveDayCreate, PlannedLeaveDayResponse, + PlannedLeaveDayUpdate, ) from app.utils.errors import NotFoundError, ValidationError @@ -39,6 +39,16 @@ async def create_planned_day( raise HTTPException(status.HTTP_422_UNPROCESSABLE_ENTITY, str(exc)) from exc +@router.get("", response_model=list[PlannedLeaveDayResponse]) +async def list_planned_days( + service: PlanningServiceDep, + _current_user: CurrentUserDep, + child_id: UUID = Query(...), +) -> list[PlannedLeaveDayResponse]: + days = await service.list_by_child(child_id) + return [_to_response(d) for d in days] + + @router.get("/{planned_leave_day_id}", response_model=PlannedLeaveDayResponse) async def get_planned_day( planned_leave_day_id: UUID, @@ -62,20 +72,22 @@ async def list_planned_days_by_workspace( return [_to_response(d) for d in days] -@router.get("/range", response_model=list[PlannedLeaveDayResponse]) -async def list_planned_days_by_range( - workspace_id: UUID = Query(...), - child_id: UUID = Query(...), - start_date: date = Query(...), - end_date: date = Query(...), - service: PlanningServiceDep = None, - _current_user: CurrentUserDep = None, -) -> list[PlannedLeaveDayResponse]: - # Inject dependencies manually for query parameters - - # Since we can't easily pass Query params with FastAPI dependencies, - # we'll need to inject them differently - pass +@router.put("/{planned_leave_day_id}", response_model=PlannedLeaveDayResponse) +async def update_planned_day( + planned_leave_day_id: UUID, + payload: PlannedLeaveDayUpdate, + service: PlanningServiceDep, + _current_user: CurrentUserDep, +) -> PlannedLeaveDayResponse: + try: + day = await service.update( + planned_leave_day_id, + day_type=payload.day_type, + is_dubbeldag=payload.is_dubbeldag, + ) + return _to_response(day) + except NotFoundError as exc: + raise HTTPException(status.HTTP_404_NOT_FOUND, str(exc)) from exc @router.delete("/{planned_leave_day_id}", status_code=status.HTTP_204_NO_CONTENT) diff --git a/backend/app/services/planning_service.py b/backend/app/services/planning_service.py index 6290dd2..416292a 100644 --- a/backend/app/services/planning_service.py +++ b/backend/app/services/planning_service.py @@ -91,3 +91,25 @@ class PlanningService: if entity is None: raise NotFoundError(f"PlannedLeaveDay {planned_leave_day_id} not found") return entity + + async def list_by_child(self, child_id: UUID) -> list[PlannedLeaveDay]: + """Get all planned days for a child across workspaces.""" + return await self._repo.list(child_id=child_id) + + async def update( + self, + planned_leave_day_id: UUID, + day_type: str | None = None, + is_dubbeldag: bool | None = None, + ) -> PlannedLeaveDay: + """Update a planned day (partial update).""" + entity = await self._repo.get_by_id(planned_leave_day_id) + if entity is None: + raise NotFoundError(f"PlannedLeaveDay {planned_leave_day_id} not found") + + if day_type is not None: + entity.day_type = day_type + if is_dubbeldag is not None: + entity.is_dubbeldag = is_dubbeldag + + return await self._repo.update(planned_leave_day_id, entity) diff --git a/backend/tests/unit/test_planning_service.py b/backend/tests/unit/test_planning_service.py index 0ef9cf5..b155971 100644 --- a/backend/tests/unit/test_planning_service.py +++ b/backend/tests/unit/test_planning_service.py @@ -226,3 +226,116 @@ class TestPlanningService: assert vacation.day_type == "vacation" assert lagsta.day_type == "lagsta" assert sjuk.day_type == "sjukpenning" + + async def test_list_by_child( + self, + planning_service: PlanningService, + seeded_ids: SeededIds, + ) -> None: + """List all planned days for a child.""" + child_id = seeded_ids.child_id + + d1 = await planning_service.add_planned_day( + workspace_id=seeded_ids.workspace_id, + child_id=child_id, + parent_personal_data_uuid=seeded_ids.personal_data_uuid, + date=date(2026, 6, 15), + day_type="vacation", + ) + d2 = await planning_service.add_planned_day( + workspace_id=seeded_ids.workspace_id, + child_id=child_id, + parent_personal_data_uuid=seeded_ids.personal_data_uuid, + date=date(2026, 6, 16), + day_type="lagsta", + ) + + result = await planning_service.list_by_child(child_id) + ids = {d.id for d in result} + assert d1.id in ids + assert d2.id in ids + + async def test_list_by_child_empty( + self, + planning_service: PlanningService, + ) -> None: + """Listing a child with no planned days returns empty.""" + result = await planning_service.list_by_child(uuid4()) + assert result == [] + + async def test_update_planned_day_day_type( + self, + planning_service: PlanningService, + seeded_ids: SeededIds, + ) -> None: + """Update the day_type of a planned day.""" + created = await planning_service.add_planned_day( + workspace_id=seeded_ids.workspace_id, + child_id=seeded_ids.child_id, + parent_personal_data_uuid=seeded_ids.personal_data_uuid, + date=date(2026, 6, 15), + day_type="vacation", + ) + + updated = await planning_service.update( + created.id, + day_type="lagsta", + ) + assert updated.day_type == "lagsta" + assert updated.date == date(2026, 6, 15) + + async def test_update_planned_day_is_dubbeldag( + self, + planning_service: PlanningService, + seeded_ids: SeededIds, + ) -> None: + """Update the is_dubbeldag flag of a planned day.""" + created = await planning_service.add_planned_day( + workspace_id=seeded_ids.workspace_id, + child_id=seeded_ids.child_id, + parent_personal_data_uuid=seeded_ids.personal_data_uuid, + date=date(2026, 6, 15), + day_type="vacation", + is_dubbeldag=False, + ) + + updated = await planning_service.update( + created.id, + is_dubbeldag=True, + ) + assert updated.is_dubbeldag is True + assert updated.day_type == "vacation" + + async def test_update_planned_day_both_fields( + self, + planning_service: PlanningService, + seeded_ids: SeededIds, + ) -> None: + """Update both day_type and is_dubbeldag.""" + created = await planning_service.add_planned_day( + workspace_id=seeded_ids.workspace_id, + child_id=seeded_ids.child_id, + parent_personal_data_uuid=seeded_ids.personal_data_uuid, + date=date(2026, 6, 15), + day_type="vacation", + is_dubbeldag=False, + ) + + updated = await planning_service.update( + created.id, + day_type="sjukpenning", + is_dubbeldag=True, + ) + assert updated.day_type == "sjukpenning" + assert updated.is_dubbeldag is True + + async def test_update_missing_raises( + self, + planning_service: PlanningService, + ) -> None: + """Updating a missing planned day raises NotFoundError.""" + with pytest.raises(NotFoundError): + await planning_service.update( + uuid4(), + day_type="lagsta", + ) diff --git a/frontend/src/services/api.test.ts b/frontend/src/services/api.test.ts new file mode 100644 index 0000000..c71ffe9 --- /dev/null +++ b/frontend/src/services/api.test.ts @@ -0,0 +1,299 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' +import * as api from './api' +import type { RawParentalLeaveFetch, RawWorkspaceOverview } from './api' + +describe('API response mappers (snake_case → camelCase)', () => { + let fetchMock: ReturnType + + beforeEach(() => { + fetchMock = vi.fn() + globalThis.fetch = fetchMock as any + }) + + afterEach(() => { + vi.clearAllMocks() + }) + + describe('getWorkspaceOverview', () => { + it('maps snake_case workspace overview to camelCase', async () => { + const rawResponse: RawWorkspaceOverview = { + workspace_id: 'ws1', + child_id: 'child1', + parent_projections: [ + { + personal_data_uuid: 'pd1', + child_id: 'child1', + birth_date: '2024-01-01', + fourth_birthday: '2028-01-01', + total_days: 480, + days_used: 100, + planned_days_before_b4: 150, + projected_left_at_b4: 230, + savable_after_b4: 96, + expiring_at_b4: 134, + }, + ], + combined_days_used: 100, + combined_days_left: 380, + combined_expiring_at_b4: 134, + } + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.getWorkspaceOverview('ws1', 'child1') + + expect(result.workspaceId).toBe('ws1') + expect(result.childId).toBe('child1') + expect(result.combinedDaysUsed).toBe(100) + expect(result.combinedDaysLeft).toBe(380) + expect(result.combinedExpiringAtB4).toBe(134) + expect(result.parentProjections.length).toBe(1) + expect(result.parentProjections[0]!.personalDataUuid).toBe('pd1') + expect(result.parentProjections[0]!.fourthBirthday).toBe('2028-01-01') + expect(result.parentProjections[0]!.plannedDaysBeforeB4).toBe(150) + expect(result.parentProjections[0]!.projectedLeftAtB4).toBe(230) + expect(result.parentProjections[0]!.savableAfterB4).toBe(96) + expect(result.parentProjections[0]!.expiringAtB4).toBe(134) + }) + }) + + describe('getParentalLeave', () => { + it('maps snake_case parental leave fetch to camelCase with nested tierData', async () => { + const rawResponse: RawParentalLeaveFetch = { + id: 'pf1', + personal_data_uuid: 'pd1', + workspace_id: 'ws1', + child_id: 'child1', + source: 'manual', + fetched_at: '2026-06-14T17:09:47', + total_days: 480, + days_used: 100, + days_left: 380, + reserved_days_total: 90, + reserved_days_left: 80, + transferable_days_left: 100, + dubbeldagar_left: 60, + tier_data: { + sjukpenning_total: 390, + sjukpenning_used: 50, + sjukpenning_left: 340, + lagsta_niva_total: 90, + lagsta_niva_used: 10, + lagsta_niva_left: 80, + }, + created_at: '2026-06-14T17:09:47', + updated_at: '2026-06-14T17:09:47', + } + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.getParentalLeave('pd1', 'child1') + + expect(result.personalDataUuid).toBe('pd1') + expect(result.workspaceId).toBe('ws1') + expect(result.childId).toBe('child1') + expect(result.fetchedAt).toBe('2026-06-14T17:09:47') + expect(result.totalDays).toBe(480) + expect(result.daysUsed).toBe(100) + expect(result.daysLeft).toBe(380) + expect(result.reservedDaysTotal).toBe(90) + expect(result.reservedDaysLeft).toBe(80) + expect(result.transferableDaysLeft).toBe(100) + expect(result.dubbeldagarLeft).toBe(60) + expect(result.createdAt).toBe('2026-06-14T17:09:47') + expect(result.updatedAt).toBe('2026-06-14T17:09:47') + + expect(result.tierData!.sjukpenningTotal).toBe(390) + expect(result.tierData!.sjukpenningUsed).toBe(50) + expect(result.tierData!.sjukpenningLeft).toBe(340) + expect(result.tierData!.lagstaNivaTotal).toBe(90) + expect(result.tierData!.lagstaNivaUsed).toBe(10) + expect(result.tierData!.lagstaNivaLeft).toBe(80) + }) + }) + + describe('getPlannedDays', () => { + it('maps snake_case planned days to camelCase', async () => { + const rawResponse = [ + { + id: 'pd1', + workspace_id: 'ws1', + child_id: 'child1', + parent_personal_data_uuid: 'pp1', + date: '2026-06-15', + day_type: 'sjukpenning' as const, + is_dubbeldag: false, + created_at: '2026-06-14T17:09:47', + updated_at: '2026-06-14T17:09:47', + }, + { + id: 'pd2', + workspace_id: 'ws1', + child_id: 'child1', + parent_personal_data_uuid: 'pp1', + date: '2026-06-16', + day_type: 'vacation' as const, + is_dubbeldag: true, + created_at: '2026-06-14T17:09:47', + updated_at: '2026-06-14T17:09:47', + }, + ] + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.getPlannedDays('child1') + + expect(result).toHaveLength(2) + expect(result[0]!.workspaceId).toBe('ws1') + expect(result[0]!.childId).toBe('child1') + expect(result[0]!.parentPersonalDataUuid).toBe('pp1') + expect(result[0]!.dayType).toBe('sjukpenning') + expect(result[0]!.isDubbeldag).toBe(false) + + expect(result[1]!.dayType).toBe('vacation') + expect(result[1]!.isDubbeldag).toBe(true) + }) + }) + + describe('getBreakPeriods', () => { + it('maps snake_case break periods to camelCase', async () => { + const rawResponse = [ + { + id: 'bp1', + workspace_id: 'ws1', + name: 'Summer 2026', + start_date: '2026-06-15', + end_date: '2026-08-31', + target_weeks: 12, + created_at: '2026-06-14T17:09:47', + updated_at: '2026-06-14T17:09:47', + }, + ] + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.getBreakPeriods('ws1') + + expect(result).toHaveLength(1) + expect(result[0]!.workspaceId).toBe('ws1') + expect(result[0]!.name).toBe('Summer 2026') + expect(result[0]!.startDate).toBe('2026-06-15') + expect(result[0]!.endDate).toBe('2026-08-31') + expect(result[0]!.targetWeeks).toBe(12) + }) + }) + + describe('getVacationDaysByWorkspace', () => { + it('maps snake_case vacation days to camelCase', async () => { + const rawResponse = [ + { + id: 'vd1', + personal_data_uuid: 'pd1', + workspace_id: 'ws1', + year: 2026, + total_days: 25, + saved_days_in: 5, + created_at: '2026-06-14T17:09:47', + updated_at: '2026-06-14T17:09:47', + }, + ] + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.getVacationDaysByWorkspace('ws1') + + expect(result).toHaveLength(1) + expect(result[0]!.personalDataUuid).toBe('pd1') + expect(result[0]!.workspaceId).toBe('ws1') + expect(result[0]!.year).toBe(2026) + expect(result[0]!.totalDays).toBe(25) + expect(result[0]!.savedDaysIn).toBe(5) + }) + }) + + describe('createBreakPeriod', () => { + it('sends camelCase request and maps snake_case response to camelCase', async () => { + const rawResponse = { + id: 'bp1', + workspace_id: 'ws1', + name: 'Summer 2026', + start_date: '2026-06-15', + end_date: '2026-08-31', + target_weeks: 12, + created_at: '2026-06-14T17:09:47', + updated_at: '2026-06-14T17:09:47', + } + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.createBreakPeriod('ws1', { + startDate: '2026-06-15', + endDate: '2026-08-31', + name: 'Summer 2026', + targetWeeks: 12, + }) + + const calls = (fetchMock.mock?.calls ?? []) as any[][] + if (calls.length === 0) throw new Error('No fetch calls made') + const [, options] = calls[0]! + const bodyStr = options.body + const body = JSON.parse(bodyStr) + + expect(body.start_date).toBe('2026-06-15') + expect(body.end_date).toBe('2026-08-31') + expect(body.target_weeks).toBe(12) + + expect(result.startDate).toBe('2026-06-15') + expect(result.endDate).toBe('2026-08-31') + expect(result.targetWeeks).toBe(12) + }) + }) + + describe('getWorkspace', () => { + it('maps snake_case workspace to camelCase', async () => { + const rawResponse = { + id: 'ws1', + name: 'My Workspace', + planning_goal: 'max_time' as const, + created_at: '2026-06-14T17:09:47', + } + + fetchMock.mockResolvedValue({ + ok: true, + status: 200, + json: async () => rawResponse, + }) + + const result = await api.getWorkspace('ws1') + + expect(result.id).toBe('ws1') + expect(result.name).toBe('My Workspace') + expect(result.planningGoal).toBe('max_time') + expect(result.createdAt).toBe('2026-06-14T17:09:47') + }) + }) +}) diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index e97a90c..2cac2a1 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -20,10 +20,212 @@ import type { WorkspaceUpdateRequest, WorkspaceOverview, } from '@/types' -import type { Workspace, BreakPeriod, Tip, PlannedLeaveDay } from '@/types' +import type { Workspace, BreakPeriod, Tip, PlannedLeaveDay, TierData, Age4Projection } from '@/types' const BASE = '/api' +// --------------------------------------------------------------------------- +// Wire format converters (snake_case → camelCase) +// --------------------------------------------------------------------------- + +export interface RawTierData { + sjukpenning_total: number + sjukpenning_used: number + sjukpenning_left: number + lagsta_niva_total: number + lagsta_niva_used: number + lagsta_niva_left: number +} + +function toTierData(raw: RawTierData): TierData { + return { + sjukpenningTotal: raw.sjukpenning_total, + sjukpenningUsed: raw.sjukpenning_used, + sjukpenningLeft: raw.sjukpenning_left, + lagstaNivaTotal: raw.lagsta_niva_total, + lagstaNivaUsed: raw.lagsta_niva_used, + lagstaNivaLeft: raw.lagsta_niva_left, + } +} + +export interface RawParentalLeaveFetch { + id: string + personal_data_uuid: string + workspace_id: string + child_id: string + source: 'scraped' | 'manual' + fetched_at: string + total_days: number + days_used: number + days_left: number + reserved_days_total: number + reserved_days_left: number + transferable_days_left: number + dubbeldagar_left: number + tier_data: RawTierData + created_at: string + updated_at: string +} + +function toParentalLeaveFetch(raw: RawParentalLeaveFetch): ParentalLeaveFetch { + return { + id: raw.id, + personalDataUuid: raw.personal_data_uuid, + workspaceId: raw.workspace_id, + childId: raw.child_id, + source: raw.source, + fetchedAt: raw.fetched_at, + totalDays: raw.total_days, + daysUsed: raw.days_used, + daysLeft: raw.days_left, + reservedDaysTotal: raw.reserved_days_total, + reservedDaysLeft: raw.reserved_days_left, + transferableDaysLeft: raw.transferable_days_left, + dubbeldagarLeft: raw.dubbeldagar_left, + tierData: toTierData(raw.tier_data), + createdAt: raw.created_at, + updatedAt: raw.updated_at, + } +} + +export interface RawAge4Projection { + personal_data_uuid: string + child_id: string + birth_date: string + fourth_birthday: string + total_days: number + days_used: number + planned_days_before_b4: number + projected_left_at_b4: number + savable_after_b4: number + expiring_at_b4: number +} + +function toAge4Projection(raw: RawAge4Projection): Age4Projection { + return { + personalDataUuid: raw.personal_data_uuid, + childId: raw.child_id, + birthDate: raw.birth_date, + fourthBirthday: raw.fourth_birthday, + totalDays: raw.total_days, + daysUsed: raw.days_used, + plannedDaysBeforeB4: raw.planned_days_before_b4, + projectedLeftAtB4: raw.projected_left_at_b4, + savableAfterB4: raw.savable_after_b4, + expiringAtB4: raw.expiring_at_b4, + } +} + +export interface RawWorkspaceOverview { + workspace_id: string + child_id: string + parent_projections: RawAge4Projection[] + combined_days_used: number + combined_days_left: number + combined_expiring_at_b4: number +} + +function toWorkspaceOverview(raw: RawWorkspaceOverview): WorkspaceOverview { + return { + workspaceId: raw.workspace_id, + childId: raw.child_id, + parentProjections: raw.parent_projections.map(toAge4Projection), + combinedDaysUsed: raw.combined_days_used, + combinedDaysLeft: raw.combined_days_left, + combinedExpiringAtB4: raw.combined_expiring_at_b4, + } +} + +interface RawVacationDays { + id: string + personal_data_uuid: string + workspace_id: string + year: number + total_days: number + saved_days_in: number + created_at: string | null + updated_at: string | null +} + +function toVacationDays(raw: RawVacationDays): VacationDays { + return { + id: raw.id, + personalDataUuid: raw.personal_data_uuid, + workspaceId: raw.workspace_id, + year: raw.year, + totalDays: raw.total_days, + savedDaysIn: raw.saved_days_in, + createdAt: raw.created_at, + updatedAt: raw.updated_at, + } +} + +interface RawPlannedLeaveDay { + id: string + workspace_id: string + child_id: string + parent_personal_data_uuid: string + date: string + day_type: 'sjukpenning' | 'lagsta' | 'vacation' + is_dubbeldag: boolean + created_at: string + updated_at: string +} + +function toPlannedLeaveDay(raw: RawPlannedLeaveDay): PlannedLeaveDay { + return { + id: raw.id, + workspaceId: raw.workspace_id, + childId: raw.child_id, + parentPersonalDataUuid: raw.parent_personal_data_uuid, + date: raw.date, + dayType: raw.day_type, + isDubbeldag: raw.is_dubbeldag, + createdAt: raw.created_at, + updatedAt: raw.updated_at, + } +} + +interface RawBreakPeriod { + id: string + workspace_id: string + name: string + start_date: string + end_date: string + target_weeks: number + created_at: string + updated_at: string +} + +function toBreakPeriod(raw: RawBreakPeriod): BreakPeriod { + return { + id: raw.id, + workspaceId: raw.workspace_id, + name: raw.name, + startDate: raw.start_date, + endDate: raw.end_date, + targetWeeks: raw.target_weeks, + createdAt: raw.created_at, + updatedAt: raw.updated_at, + } +} + +interface RawWorkspace { + id: string + name: string + planning_goal: 'max_time' | 'max_money' | 'balanced' + created_at: string +} + +function toWorkspace(raw: RawWorkspace): Workspace { + return { + id: raw.id, + name: raw.name, + planningGoal: raw.planning_goal, + createdAt: raw.created_at, + } +} + let _accessToken: string | null = null export function setAccessToken(token: string | null): void { @@ -132,7 +334,8 @@ export async function createWorkspace(req: WorkspaceCreateRequest): Promise { - return apiFetch(`/workspace/${workspaceId}`) + const raw = (await apiFetch(`/workspace/${workspaceId}`)) as RawWorkspace + return toWorkspace(raw) } export async function updateWorkspace( @@ -176,7 +379,8 @@ export async function upsertVacationDays(req: VacationDaysCreateRequest): Promis } export async function getVacationDaysByWorkspace(workspaceId: string): Promise { - return apiFetch(`/vacation-days/workspace/${workspaceId}`) + const raw = (await apiFetch(`/vacation-days/workspace/${workspaceId}`)) as RawVacationDays[] + return raw.map(toVacationDays) } // --------------------------------------------------------------------------- @@ -184,7 +388,7 @@ export async function getVacationDaysByWorkspace(workspaceId: string): Promise { - return apiFetch('/parental-leave/manual', { + const raw = (await apiFetch('/parental-leave/manual', { method: 'POST', body: JSON.stringify({ personal_data_uuid: req.personalDataUuid, @@ -201,7 +405,8 @@ export async function saveManualFetch(req: ManualFetchRequest): Promise { - return apiFetch(`/analytics/${workspaceId}/overview?child_id=${childId}`) + const raw = (await apiFetch(`/analytics/${workspaceId}/overview?child_id=${childId}`)) as RawWorkspaceOverview + return toWorkspaceOverview(raw) } // --------------------------------------------------------------------------- @@ -254,17 +460,25 @@ export async function updateChildSettings( // --------------------------------------------------------------------------- export async function getBreakPeriods(workspaceId: string): Promise { - return apiFetch(`/break-periods/${workspaceId}`) + const raw = (await apiFetch(`/break-periods/${workspaceId}`)) as RawBreakPeriod[] + return raw.map(toBreakPeriod) } export async function createBreakPeriod( workspaceId: string, payload: { startDate: string; endDate: string; name: string; targetWeeks: number }, ): Promise { - return apiFetch(`/break-periods`, { + const raw = (await apiFetch(`/break-periods`, { method: 'POST', - body: JSON.stringify({ ...payload, workspace_id: workspaceId }), - }) + body: JSON.stringify({ + workspace_id: workspaceId, + start_date: payload.startDate, + end_date: payload.endDate, + name: payload.name, + target_weeks: payload.targetWeeks, + }), + })) as RawBreakPeriod + return toBreakPeriod(raw) } export async function deleteBreakPeriod(id: string): Promise { @@ -287,7 +501,8 @@ export async function getParentalLeave( personalDataUuid: string, childId: string, ): Promise { - return apiFetch(`/parental-leave/${personalDataUuid}/${childId}`) + const raw = (await apiFetch(`/parental-leave/${personalDataUuid}/${childId}`)) as RawParentalLeaveFetch + return toParentalLeaveFetch(raw) } // --------------------------------------------------------------------------- @@ -296,7 +511,8 @@ export async function getParentalLeave( export async function getPlannedDays(childId?: string): Promise { const query = childId ? `?child_id=${childId}` : '' - return apiFetch(`/planned-days${query}`) + const raw = (await apiFetch(`/planned-days${query}`)) as RawPlannedLeaveDay[] + return raw.map(toPlannedLeaveDay) } export interface CreatePlannedDayPayload { @@ -309,10 +525,11 @@ export interface CreatePlannedDayPayload { } export async function createPlannedDay(payload: CreatePlannedDayPayload): Promise { - return apiFetch(`/planned-days`, { + const raw = (await apiFetch(`/planned-days`, { method: 'POST', body: JSON.stringify(payload), - }) + })) as RawPlannedLeaveDay + return toPlannedLeaveDay(raw) } export interface UpdatePlannedDayPayload { @@ -321,10 +538,11 @@ export interface UpdatePlannedDayPayload { } export async function updatePlannedDay(id: string, payload: UpdatePlannedDayPayload): Promise { - return apiFetch(`/planned-days/${id}`, { + const raw = (await apiFetch(`/planned-days/${id}`, { method: 'PUT', body: JSON.stringify(payload), - }) + })) as RawPlannedLeaveDay + return toPlannedLeaveDay(raw) } export async function deletePlannedDay(id: string): Promise { diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..a5cd4e4 --- /dev/null +++ b/todo.txt @@ -0,0 +1,4 @@ +- "Vacation days" i settings behöver vara så att man tex första året när man troligtvis är hemma mera, att det då är färre dagar men resterande år så har man sit "fulla pott", så att vi vet vad vi har att leka med för tid +- "Workspace Settings" makes no sense just nu. + - Ger också ett error :P när man sparar som säger "planning_goal must be one of {'max_money', 'max_time', 'balanced'}" så kanske att det borde heta så i frontend också +- "Parent Profile" går inte att ställa in hur mkt pengar man får, men vet inte heller varför detta behövs? \ No newline at end of file