Files
F-r-ldraledigplannerare/frontend/src/hooks/useLatestBalance.ts
T
jacobh2 0081f60b06 Finish the redesign's edges: auth page, modals, dead code, docs
The sign-in page becomes a plain centred column with the brand mark
and a segmented login/register switch, modals lose their last inner
boxes, the red-days list collapses every year but the current one,
and the retired components' CSS, an unused list component and 23
unreferenced translation keys are removed. ARCHITECTURE.md documents
the Almanacka styling conventions and the per-year balance rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XBRLomdLK9jF7wU7a8AA6H
2026-09-20 07:00:22 +02:00

90 lines
3.4 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import * as api from '@/services/api'
import { useLatestRequest } from './useLatestRequest'
import type { ParentBalanceInitial } from '@/components/modals/ParentBalanceForm'
import type { ParentalLeaveFetch } from '@/types'
interface UseLatestBalanceResult {
initial: ParentBalanceInitial | undefined
/** The full saved record — `source`/`fetchedAt`/`daysLeft`/`totalDays`
* that `initial` (deliberately scoped to just `ParentBalanceForm`'s
* prefill fields) doesn't carry. `null` while loading or when nothing has
* been saved yet; consumers that show a status line (`BalanceFlow`'s
* per-member row) read this instead of `initial`. */
raw: ParentalLeaveFetch | null
isLoading: boolean
error: string | null
/** Re-runs the fetch — used after an FK scrape completes so a row already
* on screen picks up the newly persisted balance without a remount. */
refresh: () => Promise<void>
}
/**
* Loads a parent's latest saved parental-leave balance so `ParentBalanceForm`
* can be reopened pre-filled with the real figures instead of always
* falling back to its 195/45/60 preset. Shared by `Settings` ("Dagar kvar")
* and `Dashboard` ("Ange manuellt") so a saved balance shows the same way
* from either entry point.
*
* Imported directly from this file (not the `@/hooks` barrel) by both pages
* so their tests can drive it through a mocked `api.getParentalLeave`
* instead of mocking the hook itself, while the rest of `@/hooks` stays
* blanket-mocked.
*
* `personalDataUuid`/`childId` are null when no balance form is open — the
* hook then no-ops and reports no saved balance, rather than fetching.
*/
export function useLatestBalance(
personalDataUuid: string | null,
childId: string | null,
): UseLatestBalanceResult {
const [initial, setInitial] = useState<ParentBalanceInitial | undefined>(undefined)
const [raw, setRaw] = useState<ParentalLeaveFetch | null>(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const { start, isCurrent } = useLatestRequest()
const fetch = useCallback(async () => {
const requestId = start()
if (!personalDataUuid || !childId) {
setInitial(undefined)
setRaw(null)
setError(null)
return
}
setIsLoading(true)
setError(null)
try {
const response = await api.getParentalLeave(personalDataUuid, childId)
if (!isCurrent(requestId)) return
// `null` means nothing has been saved for this parent/child yet (a
// brand-new partner, or self before any fetch) — not an error, so the
// form is left to fall back to its own preset.
setInitial(
response
? {
sjukpenningLeft: response.tierData.sjukpenningLeft,
lagstaNivaLeft: response.tierData.lagstaNivaLeft,
dubbeldagarLeft: response.dubbeldagarLeft,
reservedDaysLeft: response.reservedDaysLeft,
}
: undefined,
)
setRaw(response)
} catch (err) {
if (!isCurrent(requestId)) return
setError(err instanceof Error ? err.message : 'Failed to load parental leave balance')
setInitial(undefined)
setRaw(null)
} finally {
if (isCurrent(requestId)) setIsLoading(false)
}
}, [personalDataUuid, childId, start, isCurrent])
useEffect(() => {
void fetch()
}, [fetch])
return { initial, raw, isLoading, error, refresh: fetch }
}