Let a household choose to be paid for the red days

Asking for a full week off never spent anything on the Saturday, the
Sunday or a holiday, and a test now says so outright rather than leaving
it to be rediscovered. Those days are free because vacation cannot be
taken on a day that is not a working day.

Parental benefit is a payment rather than a schedule, though, so it can
be drawn on a red day to turn an unpaid day off into a paid one — at the
cost of a day the family would otherwise keep. That is now a workspace
setting, off by default, and it never reaches for a vacation day. With it
off the planner behaves exactly as before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ei7gCZzdd751uZtPzb1Pep
This commit is contained in:
2026-09-19 14:28:38 +02:00
parent 1837154225
commit b87ad0e7ad
18 changed files with 511 additions and 10 deletions
+65
View File
@@ -647,6 +647,71 @@ describe('Settings', () => {
expect(setShowWeekNumbers).toHaveBeenCalledWith(true)
})
describe('take PL on red days', () => {
it('renders unchecked by default', () => {
renderSettings()
expect(screen.getByTestId('take-pl-on-red-days-checkbox')).not.toBeChecked()
})
it('renders checked when the workspace has it enabled', () => {
vi.mocked(useWorkspace).mockReturnValue(
makeWorkspaceContextValue({
workspace: {
id: 'ws-1',
name: 'Test Workspace',
createdAt: '2026-01-01',
takePlOnRedDays: true,
},
workspaceId: 'ws-1',
children: [{ id: 'c-1', name: 'Child 1', birth_date: CHILD_BIRTH_DATE }],
selectedChildId: 'c-1',
personalDataUuid: 'pd-1',
parentName: 'Test Parent',
})
)
renderSettings()
expect(screen.getByTestId('take-pl-on-red-days-checkbox')).toBeChecked()
})
it('saves the setting and refreshes the workspace context on toggle', async () => {
vi.mocked(api.updateWorkspace).mockResolvedValue({
id: 'ws-1',
name: 'Test Workspace',
createdAt: '2026-01-01',
takePlOnRedDays: true,
})
const mockRefreshContext = vi.fn().mockResolvedValue(undefined)
vi.mocked(useWorkspace).mockReturnValue(
makeWorkspaceContextValue({
workspace: { id: 'ws-1', name: 'Test Workspace', createdAt: '2026-01-01' },
workspaceId: 'ws-1',
children: [{ id: 'c-1', name: 'Child 1', birth_date: CHILD_BIRTH_DATE }],
selectedChildId: 'c-1',
personalDataUuid: 'pd-1',
parentName: 'Test Parent',
refreshContext: mockRefreshContext,
})
)
renderSettings()
fireEvent.click(screen.getByTestId('take-pl-on-red-days-checkbox'))
await waitFor(() =>
expect(api.updateWorkspace).toHaveBeenCalledWith('ws-1', { takePlOnRedDays: true })
)
expect(mockRefreshContext).toHaveBeenCalled()
})
it('shows an error message when saving fails', async () => {
vi.mocked(api.updateWorkspace).mockRejectedValue(new Error('network down'))
renderSettings()
fireEvent.click(screen.getByTestId('take-pl-on-red-days-checkbox'))
expect(await screen.findByTestId('save-message')).toHaveTextContent('network down')
})
})
describe('language selector', () => {
// Node's own experimental `localStorage` global shadows jsdom's working
// implementation in this environment and throws on every call — stub a
+25
View File
@@ -144,6 +144,7 @@ export function Settings(): ReactNode {
const [memberActionError, setMemberActionError] = useState<{ uuid: string; text: string } | null>(null)
const [balanceFormUuid, setBalanceFormUuid] = useState<string | null>(null)
const [isSavingBalance, setIsSavingBalance] = useState(false)
const [isSavingRedDayPref, setIsSavingRedDayPref] = useState(false)
// Settings isn't scoped to a single child the way Onboarding/Dashboard are
// — mirrors the fallback `handleSaveBalance` below uses when saving.
@@ -361,6 +362,19 @@ export function Settings(): ReactNode {
}
}
const handleToggleTakePlOnRedDays = async (checked: boolean) => {
if (!workspace?.id) return
try {
setIsSavingRedDayPref(true)
await api.updateWorkspace(workspace.id, { takePlOnRedDays: checked })
await refreshContext()
} catch (err) {
showMessage('error', err instanceof Error ? err.message : t('settings.takePlOnRedDaysError'))
} finally {
setIsSavingRedDayPref(false)
}
}
const handleOpenBalanceForm = (uuid: string) => {
setBalanceFormUuid(uuid)
}
@@ -621,6 +635,17 @@ export function Settings(): ReactNode {
/>
<span>{t('settings.showWeekNumbers')}</span>
</label>
<label className="settings-page__checkbox-label">
<input
type="checkbox"
checked={workspace?.takePlOnRedDays ?? false}
onChange={(e) => handleToggleTakePlOnRedDays(e.target.checked)}
disabled={isSavingRedDayPref}
data-testid="take-pl-on-red-days-checkbox"
/>
<span>{t('settings.takePlOnRedDays')}</span>
</label>
<p className="settings-page__help">{t('settings.takePlOnRedDaysHelp')}</p>
</section>
<section className="settings-page__section">