Group settings into four panels and give balances one home
Settings becomes Barn & familj, Saldon, Kalender & språk and Undantag & röda dagar, each saving on its own. A BalanceFlow component owns fetching from Försäkringskassan and manual entry for every parent, used by the Saldon panel and by the onboarding wizard, which now runs without the app chrome. The red-days list and the day-override form merge into one exceptions view, the vacation table becomes wrapping rows that fit a phone, and the BankID modal speaks translated status lines instead of emoji. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XBRLomdLK9jF7wU7a8AA6H
This commit is contained in:
+109
-196
@@ -9,22 +9,17 @@ import {
|
||||
useWorkspaceMembers,
|
||||
} from '@/hooks'
|
||||
import * as api from '@/services/api'
|
||||
// Imported directly (not via the `@/hooks` barrel) so this page's tests can
|
||||
// drive it through a mocked `api.getParentalLeave` — see the hook's doc
|
||||
// comment for why.
|
||||
import { useLatestBalance } from '@/hooks/useLatestBalance'
|
||||
import { isApiError } from '@/services/errors'
|
||||
import { useTranslation, type Locale, type TranslationKey } from '@/i18n'
|
||||
import { Layout } from '@/components/layout/Layout'
|
||||
import { LoadingSpinner } from '@/components/common/LoadingSpinner'
|
||||
import { PageSkeleton } from '@/components/common/PageSkeleton'
|
||||
import { ChildBirthdayInput } from '@/components/inputs/ChildBirthdayInput'
|
||||
import { ParentProfileInput, type SalaryType, type EmploymentStatus } from '@/components/inputs/ParentProfileInput'
|
||||
import { VacationDaysInput } from '@/components/inputs/VacationDaysInput'
|
||||
import { VacationDaysTable, type VacationDaysTableRow } from '@/components/vacation-days/VacationDaysTable'
|
||||
import { DayOverridesInput } from '@/components/inputs/DayOverridesInput'
|
||||
import { RedDaysList } from '@/components/calendar/RedDaysList'
|
||||
import { ParentBalanceForm } from '@/components/modals/ParentBalanceForm'
|
||||
import { CalendarExceptions } from '@/components/calendar/CalendarExceptions'
|
||||
import { BalanceFlow } from '@/components/balance/BalanceFlow'
|
||||
import './settings.css'
|
||||
|
||||
const RED_DAYS_WINDOW_YEARS = 4
|
||||
|
||||
@@ -137,23 +132,23 @@ export function Settings(): ReactNode {
|
||||
const [preschoolPlanningDays, setPreschoolPlanningDays] = useState(6)
|
||||
const [parentData, setParentData] = useState<{ salaryType?: SalaryType; employmentStatus?: EmploymentStatus }>({})
|
||||
const [saveMessage, setSaveMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
// Each panel owns exactly one `isSaving` flag, scoped to its own actions —
|
||||
// previously a single page-wide `isSaving` meant saving a vacation year
|
||||
// (Saldon panel) also disabled the "Spara" button in the family panel,
|
||||
// and vice versa.
|
||||
const [isSavingFamily, setIsSavingFamily] = useState(false)
|
||||
const [isSavingVacation, setIsSavingVacation] = useState(false)
|
||||
const [isSavingRedDayPref, setIsSavingRedDayPref] = useState(false)
|
||||
const [newPartnerName, setNewPartnerName] = useState('')
|
||||
const [renamingUuid, setRenamingUuid] = useState<string | null>(null)
|
||||
const [renameValue, setRenameValue] = useState('')
|
||||
const [removeConfirmUuid, setRemoveConfirmUuid] = useState<string | null>(null)
|
||||
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.
|
||||
// — the balance flow needs somewhere to attach a saved balance even before
|
||||
// a child is chosen/created elsewhere in the app.
|
||||
const balanceChildId = selectedChildId ?? children[0]?.id ?? null
|
||||
const { initial: balanceInitial, isLoading: balanceInitialLoading } = useLatestBalance(
|
||||
balanceFormUuid,
|
||||
balanceChildId,
|
||||
)
|
||||
|
||||
const isLoading = wsLoading || vacLoading || childDetailsLoading || dayOverridesLoading
|
||||
|
||||
@@ -164,6 +159,15 @@ export function Settings(): ReactNode {
|
||||
}
|
||||
}, [childDetails])
|
||||
|
||||
// `#saldon` in the URL scrolls that panel into view once the page's
|
||||
// content has actually mounted.
|
||||
useEffect(() => {
|
||||
if (isLoading) return
|
||||
if (window.location.hash === '#saldon') {
|
||||
document.getElementById('saldon')?.scrollIntoView()
|
||||
}
|
||||
}, [isLoading])
|
||||
|
||||
const selectedChild = children.find((c) => c.id === selectedChildId)
|
||||
const hasPartner = members.some((m) => m.uuid !== personalDataUuid)
|
||||
|
||||
@@ -206,20 +210,29 @@ export function Settings(): ReactNode {
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
}
|
||||
|
||||
const handleSaveChild = async () => {
|
||||
// One combined save for the "Barn & familj" panel's editable fields (child
|
||||
// birth date/preschool days, own salary/employment) — one button, one
|
||||
// `isSavingFamily` flag, matching the rest of that panel's actions below.
|
||||
const handleSaveFamily = async () => {
|
||||
try {
|
||||
setIsSavingFamily(true)
|
||||
if (selectedChildId && childBirthDate) {
|
||||
setIsSaving(true)
|
||||
await updateChildDetails({
|
||||
birthDate: childBirthDate,
|
||||
preschoolPlanningDaysPerYear: preschoolPlanningDays,
|
||||
})
|
||||
showMessage('success', t('settings.saveChildSuccess'))
|
||||
}
|
||||
if (personalDataUuid) {
|
||||
await api.updateParentSettings(personalDataUuid, {
|
||||
salary_type: parentData.salaryType,
|
||||
employment_status: parentData.employmentStatus,
|
||||
})
|
||||
}
|
||||
showMessage('success', t('settings.saveFamilySuccess'))
|
||||
} catch (err) {
|
||||
showMessage('error', err instanceof Error ? err.message : t('settings.saveChildError'))
|
||||
showMessage('error', err instanceof Error ? err.message : t('settings.saveFamilyError'))
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
setIsSavingFamily(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,27 +256,10 @@ export function Settings(): ReactNode {
|
||||
})
|
||||
}
|
||||
|
||||
const handleSaveParentProfile = async () => {
|
||||
try {
|
||||
if (personalDataUuid) {
|
||||
setIsSaving(true)
|
||||
await api.updateParentSettings(personalDataUuid, {
|
||||
salary_type: parentData.salaryType,
|
||||
employment_status: parentData.employmentStatus,
|
||||
})
|
||||
showMessage('success', t('settings.saveProfileSuccess'))
|
||||
}
|
||||
} catch (err) {
|
||||
showMessage('error', err instanceof Error ? err.message : t('settings.saveProfileError'))
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveVacationYear = async (year: number, totalDays: number, savedDaysIn: number) => {
|
||||
try {
|
||||
if (workspace?.id && personalDataUuid) {
|
||||
setIsSaving(true)
|
||||
setIsSavingVacation(true)
|
||||
await upsertVacationDays({
|
||||
personalDataUuid,
|
||||
workspaceId: workspace.id,
|
||||
@@ -276,7 +272,7 @@ export function Settings(): ReactNode {
|
||||
} catch (err) {
|
||||
showMessage('error', err instanceof Error ? err.message : t('settings.saveVacationError'))
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
setIsSavingVacation(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,7 +291,7 @@ export function Settings(): ReactNode {
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true)
|
||||
setIsSavingFamily(true)
|
||||
setSaveMessage(null)
|
||||
await api.addPartner(workspace.id, newPartnerName)
|
||||
await refreshMembers()
|
||||
@@ -304,7 +300,7 @@ export function Settings(): ReactNode {
|
||||
} catch (err) {
|
||||
showMessage('error', describePartnerError(err, t, t('settings.addPartnerError')))
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
setIsSavingFamily(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,7 +322,7 @@ export function Settings(): ReactNode {
|
||||
return
|
||||
}
|
||||
try {
|
||||
setIsSaving(true)
|
||||
setIsSavingFamily(true)
|
||||
setMemberActionError(null)
|
||||
await api.renameWorkspaceParent(workspace.id, uuid, renameValue)
|
||||
await refreshMembers()
|
||||
@@ -338,7 +334,7 @@ export function Settings(): ReactNode {
|
||||
} catch (err) {
|
||||
setMemberActionError({ uuid, text: describePartnerError(err, t, t('settings.renamePartnerError')) })
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
setIsSavingFamily(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,7 +351,7 @@ export function Settings(): ReactNode {
|
||||
const handleConfirmRemove = async (uuid: string) => {
|
||||
if (!workspace?.id) return
|
||||
try {
|
||||
setIsSaving(true)
|
||||
setIsSavingFamily(true)
|
||||
setMemberActionError(null)
|
||||
await api.removeWorkspaceParent(workspace.id, uuid)
|
||||
setRemoveConfirmUuid(null)
|
||||
@@ -363,7 +359,7 @@ export function Settings(): ReactNode {
|
||||
} catch (err) {
|
||||
setMemberActionError({ uuid, text: describePartnerError(err, t, t('settings.removePartnerError')) })
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
setIsSavingFamily(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,44 +376,6 @@ export function Settings(): ReactNode {
|
||||
}
|
||||
}
|
||||
|
||||
const handleOpenBalanceForm = (uuid: string) => {
|
||||
setBalanceFormUuid(uuid)
|
||||
}
|
||||
|
||||
// No try/catch around the save call — a rejection propagates to
|
||||
// `ParentBalanceForm`'s own `onSave` handler, which owns showing it
|
||||
// (inline under the offending field for a mapped 422, or its own top
|
||||
// banner otherwise).
|
||||
const handleSaveBalance = async (data: {
|
||||
sjukpenningLeft: number
|
||||
lagstaNivaLeft: number
|
||||
dubbeldagarLeft: number
|
||||
reservedDaysLeft: number | null
|
||||
}) => {
|
||||
// Settings isn't scoped to a single child the way Onboarding/Dashboard
|
||||
// are guaranteed to be — `selectedChildId` can be unset before a child
|
||||
// is chosen elsewhere in the app. Fall back to the workspace's first
|
||||
// child so the balance still has somewhere to attach.
|
||||
const childId = selectedChildId ?? children[0]?.id
|
||||
if (!workspace?.id || !balanceFormUuid || !childId) return
|
||||
try {
|
||||
setIsSavingBalance(true)
|
||||
await api.saveManualFetch({
|
||||
personalDataUuid: balanceFormUuid,
|
||||
workspaceId: workspace.id,
|
||||
childId,
|
||||
sjukpenningLeft: data.sjukpenningLeft,
|
||||
lagstaNivaLeft: data.lagstaNivaLeft,
|
||||
dubbeldagarLeft: data.dubbeldagarLeft,
|
||||
reservedDaysLeft: data.reservedDaysLeft,
|
||||
source: 'manual',
|
||||
})
|
||||
setBalanceFormUuid(null)
|
||||
} finally {
|
||||
setIsSavingBalance(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div data-testid="settings-page" className="settings-page">
|
||||
@@ -433,10 +391,10 @@ export function Settings(): ReactNode {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.childSectionTitle')}</h2>
|
||||
<section className="settings-panel">
|
||||
<h2>{t('settings.familySectionTitle')}</h2>
|
||||
{selectedChild ? (
|
||||
<>
|
||||
<div className="settings-panel__block">
|
||||
<p>
|
||||
{t('settings.selectedChildLabel')} <strong>{selectedChild.name}</strong>
|
||||
</p>
|
||||
@@ -445,8 +403,8 @@ export function Settings(): ReactNode {
|
||||
onBirthDateChange={setChildBirthDate}
|
||||
label={t('settings.childBirthDateLabel')}
|
||||
/>
|
||||
<div className="settings-page__field">
|
||||
<p className="settings-page__help">{t('settings.preschoolHelp')}</p>
|
||||
<div className="field settings-panel__field">
|
||||
<p className="settings-panel__help">{t('settings.preschoolHelp')}</p>
|
||||
<VacationDaysInput
|
||||
label={t('settings.preschoolPlanningDaysLabel')}
|
||||
value={preschoolPlanningDays}
|
||||
@@ -455,43 +413,34 @@ export function Settings(): ReactNode {
|
||||
max={30}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSaveChild}
|
||||
className="btn btn--primary"
|
||||
disabled={isSaving}
|
||||
data-testid="save-child-button"
|
||||
>
|
||||
{isSaving ? t('common.saving') : t('settings.saveChildButton')}
|
||||
</button>
|
||||
</>
|
||||
</div>
|
||||
) : (
|
||||
<p>{t('settings.noChildSelected')}</p>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.profileSectionTitle')}</h2>
|
||||
<p className="settings-page__help">{t('settings.profileHelp')}</p>
|
||||
<h3>{t('settings.profileSectionTitle')}</h3>
|
||||
<p className="settings-panel__help">{t('settings.profileHelp')}</p>
|
||||
<ParentProfileInput parent={parentData} onChange={handleParentChange} />
|
||||
<button
|
||||
onClick={handleSaveParentProfile}
|
||||
className="btn btn--primary"
|
||||
disabled={isSaving}
|
||||
data-testid="save-parent-button"
|
||||
>
|
||||
{isSaving ? t('common.saving') : t('settings.saveProfileButton')}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.partnerSectionTitle')}</h2>
|
||||
<div className="actions settings-panel__actions">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleSaveFamily()}
|
||||
className="btn btn--primary"
|
||||
disabled={isSavingFamily}
|
||||
data-testid="save-family-button"
|
||||
>
|
||||
{isSavingFamily ? t('common.saving') : t('settings.saveFamilyButton')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<h3>{t('settings.partnerSectionTitle')}</h3>
|
||||
<p>{hasPartner ? t('settings.partnerLinkedIntro') : t('settings.partnerIntro')}</p>
|
||||
|
||||
{membersLoading ? (
|
||||
<p>{t('settings.membersLoading')}</p>
|
||||
) : (
|
||||
<ul className="settings-page__member-list" data-testid="member-list">
|
||||
<ul className="settings-panel__member-list" data-testid="member-list">
|
||||
{members.map((member) => {
|
||||
const isSelf = member.uuid === personalDataUuid
|
||||
const isRenaming = renamingUuid === member.uuid
|
||||
@@ -499,7 +448,7 @@ export function Settings(): ReactNode {
|
||||
return (
|
||||
<li
|
||||
key={member.uuid}
|
||||
className="settings-page__member-row"
|
||||
className="settings-panel__member-row"
|
||||
data-testid={`member-row-${member.uuid}`}
|
||||
>
|
||||
{isRenaming ? (
|
||||
@@ -508,14 +457,14 @@ export function Settings(): ReactNode {
|
||||
type="text"
|
||||
value={renameValue}
|
||||
onChange={(e) => setRenameValue(e.target.value)}
|
||||
disabled={isSaving}
|
||||
className="field__control settings-page__partner-input"
|
||||
disabled={isSavingFamily}
|
||||
className="field__control settings-panel__partner-input"
|
||||
data-testid={`rename-input-${member.uuid}`}
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleSaveRename(member.uuid)}
|
||||
className="btn btn--secondary btn--sm"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid={`rename-save-${member.uuid}`}
|
||||
>
|
||||
{t('settings.saveRenameButton')}
|
||||
@@ -523,7 +472,7 @@ export function Settings(): ReactNode {
|
||||
<button
|
||||
onClick={handleCancelRename}
|
||||
className="btn btn--secondary btn--sm"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid={`rename-cancel-${member.uuid}`}
|
||||
>
|
||||
{t('settings.cancelButton')}
|
||||
@@ -540,27 +489,19 @@ export function Settings(): ReactNode {
|
||||
<button
|
||||
onClick={() => handleStartRename(member)}
|
||||
className="btn btn--secondary btn--sm"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid={`rename-button-${member.uuid}`}
|
||||
>
|
||||
{t('settings.renameButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleOpenBalanceForm(member.uuid)}
|
||||
className="btn btn--secondary btn--sm"
|
||||
disabled={isSaving}
|
||||
data-testid={`settings-balance-${member.uuid}`}
|
||||
>
|
||||
{t('settings.balanceButton')}
|
||||
</button>
|
||||
{!isSelf &&
|
||||
(isConfirmingRemove ? (
|
||||
<span className="settings-page__remove-confirm">
|
||||
<span className="settings-panel__remove-confirm">
|
||||
{t('settings.removeConfirmPrompt', { name: member.name })}
|
||||
<button
|
||||
onClick={() => handleConfirmRemove(member.uuid)}
|
||||
className="btn btn--danger btn--sm"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid={`remove-confirm-${member.uuid}`}
|
||||
>
|
||||
{t('settings.removeConfirmButton')}
|
||||
@@ -568,7 +509,7 @@ export function Settings(): ReactNode {
|
||||
<button
|
||||
onClick={handleCancelRemove}
|
||||
className="btn btn--secondary btn--sm"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid={`remove-cancel-${member.uuid}`}
|
||||
>
|
||||
{t('settings.cancelButton')}
|
||||
@@ -578,7 +519,7 @@ export function Settings(): ReactNode {
|
||||
<button
|
||||
onClick={() => handleRequestRemove(member.uuid)}
|
||||
className="btn btn--danger btn--sm"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid={`remove-button-${member.uuid}`}
|
||||
>
|
||||
{t('settings.removePartnerButton')}
|
||||
@@ -602,36 +543,49 @@ export function Settings(): ReactNode {
|
||||
)}
|
||||
|
||||
{!hasPartner && (
|
||||
<div className="settings-page__partner-row">
|
||||
<div className="settings-panel__partner-row">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('settings.partnerNamePlaceholder')}
|
||||
value={newPartnerName}
|
||||
onChange={(e) => setNewPartnerName(e.target.value)}
|
||||
disabled={isSaving}
|
||||
className="field__control settings-page__partner-input"
|
||||
disabled={isSavingFamily}
|
||||
className="field__control settings-panel__partner-input"
|
||||
/>
|
||||
<button
|
||||
onClick={handleAddPartner}
|
||||
onClick={() => void handleAddPartner()}
|
||||
className="btn btn--primary"
|
||||
disabled={isSaving}
|
||||
disabled={isSavingFamily}
|
||||
data-testid="add-partner-button"
|
||||
>
|
||||
{isSaving ? t('settings.addingPartner') : t('settings.addPartnerButton')}
|
||||
{isSavingFamily ? t('settings.addingPartner') : t('settings.addPartnerButton')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.vacationSectionTitle')}</h2>
|
||||
<p className="settings-page__help">{t('settings.vacationHelp')}</p>
|
||||
<VacationDaysTable rows={vacationRows} onSave={handleSaveVacationYear} isSaving={isSaving} />
|
||||
<section id="saldon" className="settings-panel">
|
||||
<h2>{t('settings.balanceSectionTitle')}</h2>
|
||||
{membersLoading ? (
|
||||
<p>{t('settings.membersLoading')}</p>
|
||||
) : (
|
||||
<BalanceFlow
|
||||
members={members}
|
||||
personalDataUuid={personalDataUuid}
|
||||
workspaceId={workspace?.id ?? ''}
|
||||
selectedChildId={balanceChildId}
|
||||
mode="panel"
|
||||
onSaved={() => void refreshMembers()}
|
||||
/>
|
||||
)}
|
||||
|
||||
<p className="settings-panel__help">{t('settings.vacationHelp')}</p>
|
||||
<VacationDaysTable rows={vacationRows} onSave={handleSaveVacationYear} isSaving={isSavingVacation} />
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.calendarSectionTitle')}</h2>
|
||||
<label className="settings-page__checkbox-label">
|
||||
<section className="settings-panel">
|
||||
<h2>{t('settings.calendarLanguageSectionTitle')}</h2>
|
||||
<label className="settings-panel__checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={showWeekNumbers}
|
||||
@@ -640,27 +594,24 @@ export function Settings(): ReactNode {
|
||||
/>
|
||||
<span>{t('settings.showWeekNumbers')}</span>
|
||||
</label>
|
||||
<label className="settings-page__checkbox-label">
|
||||
<label className="settings-panel__checkbox-label">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={workspace?.takePlOnRedDays ?? false}
|
||||
onChange={(e) => handleToggleTakePlOnRedDays(e.target.checked)}
|
||||
onChange={(e) => void 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>
|
||||
<p className="settings-panel__help">{t('settings.takePlOnRedDaysHelp')}</p>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.languageSectionTitle')}</h2>
|
||||
<label htmlFor="language-select" className="settings-page__label">
|
||||
<label htmlFor="language-select" className="settings-panel__label">
|
||||
{t('settings.languageLabel')}
|
||||
</label>
|
||||
<select
|
||||
id="language-select"
|
||||
className="field__control settings-page__select"
|
||||
className="field__control settings-panel__select"
|
||||
value={explicitLocale ?? 'auto'}
|
||||
onChange={(e) => handleLanguageChange(e.target.value)}
|
||||
data-testid="language-select"
|
||||
@@ -671,22 +622,9 @@ export function Settings(): ReactNode {
|
||||
</select>
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.dayOverridesSectionTitle')}</h2>
|
||||
<p className="settings-page__help">{t('settings.dayOverridesHelp')}</p>
|
||||
<DayOverridesInput
|
||||
overrides={dayOverrides}
|
||||
isLoading={dayOverridesLoading || isSaving}
|
||||
onAdd={upsertDayOverride}
|
||||
onRemove={removeDayOverride}
|
||||
workspaceId={workspace?.id ?? ''}
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>{t('settings.redDaysSectionTitle')}</h2>
|
||||
<p className="settings-page__help">{t('settings.redDaysHelp')}</p>
|
||||
<RedDaysList
|
||||
<section className="settings-panel">
|
||||
<h2>{t('settings.exceptionsSectionTitle')}</h2>
|
||||
<CalendarExceptions
|
||||
days={calendarWindow?.days ?? []}
|
||||
startYear={redDaysStartYear}
|
||||
years={RED_DAYS_WINDOW_YEARS}
|
||||
@@ -703,31 +641,6 @@ export function Settings(): ReactNode {
|
||||
onRemoveOverride={removeDayOverride}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{balanceFormUuid &&
|
||||
(() => {
|
||||
const balanceFormMember = members.find((m) => m.uuid === balanceFormUuid)
|
||||
if (!balanceFormMember) return null
|
||||
if (balanceInitialLoading) {
|
||||
return (
|
||||
<div className="modal__overlay" data-testid="settings-balance-loading">
|
||||
<LoadingSpinner message={t('parentBalance.loadingBalance')} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ParentBalanceForm
|
||||
parentName={balanceFormMember.name}
|
||||
isSelf={balanceFormMember.uuid === personalDataUuid}
|
||||
totalDays={members.length >= 2 ? 240 : 480}
|
||||
initial={balanceInitial}
|
||||
onSave={handleSaveBalance}
|
||||
onClose={() => setBalanceFormUuid(null)}
|
||||
isSaving={isSavingBalance}
|
||||
error={null}
|
||||
/>
|
||||
)
|
||||
})()}
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user