MVP frontend+backend
This commit is contained in:
+155
-24
@@ -1,6 +1,7 @@
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { ReactNode, useState, useEffect } from 'react'
|
||||
import { useWorkspace } from '@/context'
|
||||
import { useVacationDays, useBreakPeriods } from '@/hooks'
|
||||
import * as api from '@/services/api'
|
||||
import { Layout } from '@/components/layout/Layout'
|
||||
import { LoadingSpinner } from '@/components/common/LoadingSpinner'
|
||||
import { ChildBirthdayInput } from '@/components/inputs/ChildBirthdayInput'
|
||||
@@ -10,7 +11,7 @@ import { VacationDaysInput } from '@/components/inputs/VacationDaysInput'
|
||||
import { BreakPeriodsInput } from '@/components/inputs/BreakPeriodsInput'
|
||||
|
||||
export function Settings(): ReactNode {
|
||||
const { workspace, isLoading: wsLoading } = useWorkspace()
|
||||
const { workspace, isLoading: wsLoading, selectedChildId, children, personalDataUuid, refreshContext } = useWorkspace()
|
||||
const { data: vacDays, isLoading: vacLoading, upsert } = useVacationDays(workspace?.id ?? '')
|
||||
const { data: breakPeriods, isLoading: bpLoading, add: addBreakPeriod, remove: removeBreakPeriod } = useBreakPeriods(
|
||||
workspace?.id ?? null
|
||||
@@ -19,20 +20,38 @@ export function Settings(): ReactNode {
|
||||
const [childBirthDate, setChildBirthDate] = useState('')
|
||||
const [parentData, setParentData] = useState({ salaryType: '', employmentStatus: '' })
|
||||
const [vacationDaysValue, setVacationDaysValue] = useState(vacDays?.[0]?.totalDays ?? 25)
|
||||
const [planningGoal, setPlanningGoal] = useState(workspace?.planningGoal ?? 'balance')
|
||||
const [planningGoal, setPlanningGoal] = useState<'max_time' | 'max_money' | 'balanced'>(
|
||||
(workspace?.planningGoal as 'max_time' | 'max_money' | 'balanced') ?? 'balanced',
|
||||
)
|
||||
const [saveMessage, setSaveMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
const isLoading = wsLoading || vacLoading || bpLoading
|
||||
|
||||
useEffect(() => {
|
||||
if (vacDays?.[0]?.totalDays) {
|
||||
setVacationDaysValue(vacDays[0].totalDays)
|
||||
}
|
||||
}, [vacDays])
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace?.planningGoal) {
|
||||
setPlanningGoal(workspace.planningGoal)
|
||||
}
|
||||
}, [workspace?.planningGoal])
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingSpinner message="Loading settings..." />
|
||||
}
|
||||
|
||||
const selectedChild = children.find((c) => c.id === selectedChildId)
|
||||
|
||||
const handleSaveVacationDays = async () => {
|
||||
try {
|
||||
if (workspace?.id) {
|
||||
if (workspace?.id && personalDataUuid) {
|
||||
setIsSaving(true)
|
||||
await upsert({
|
||||
personalDataUuid: 'current-user',
|
||||
personalDataUuid,
|
||||
workspaceId: workspace.id,
|
||||
year: new Date().getFullYear(),
|
||||
totalDays: vacationDaysValue,
|
||||
@@ -40,30 +59,112 @@ export function Settings(): ReactNode {
|
||||
setSaveMessage({ type: 'success', text: 'Vacation days saved!' })
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
}
|
||||
} catch {
|
||||
setSaveMessage({ type: 'error', text: 'Failed to save vacation days' })
|
||||
} catch (err) {
|
||||
setSaveMessage({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : 'Failed to save vacation days',
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddBreakPeriod = async (period: any) => {
|
||||
const handleSaveParentProfile = async () => {
|
||||
try {
|
||||
if (personalDataUuid) {
|
||||
setIsSaving(true)
|
||||
await api.updateParentSettings(personalDataUuid, {
|
||||
salary_type: parentData.salaryType || undefined,
|
||||
employment_status: parentData.employmentStatus || undefined,
|
||||
})
|
||||
setSaveMessage({ type: 'success', text: 'Parent profile saved!' })
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
}
|
||||
} catch (err) {
|
||||
setSaveMessage({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : 'Failed to save parent profile',
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSavePlanningGoal = async () => {
|
||||
try {
|
||||
if (workspace?.id) {
|
||||
await addBreakPeriod(
|
||||
period.startDate,
|
||||
period.endDate,
|
||||
period.name,
|
||||
period.targetWeeks
|
||||
)
|
||||
setIsSaving(true)
|
||||
await api.updateWorkspace(workspace.id, {
|
||||
planningGoal: planningGoal as 'max_time' | 'max_money' | 'balanced',
|
||||
})
|
||||
await refreshContext()
|
||||
setSaveMessage({ type: 'success', text: 'Planning goal saved!' })
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
}
|
||||
} catch (err) {
|
||||
setSaveMessage({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : 'Failed to save planning goal',
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleSaveChildBirthDate = async () => {
|
||||
try {
|
||||
if (selectedChildId && childBirthDate) {
|
||||
setIsSaving(true)
|
||||
await api.updateChildSettings(selectedChildId, { birth_date: childBirthDate })
|
||||
setSaveMessage({ type: 'success', text: 'Child birth date saved!' })
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
}
|
||||
} catch (err) {
|
||||
setSaveMessage({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : 'Failed to save child birth date',
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleAddBreakPeriod = async (period: { name: string; startDate: string; endDate: string; targetWeeks: number }) => {
|
||||
try {
|
||||
if (workspace?.id) {
|
||||
setIsSaving(true)
|
||||
await addBreakPeriod(period.startDate, period.endDate, period.name, period.targetWeeks)
|
||||
setSaveMessage({ type: 'success', text: 'Break period added!' })
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
}
|
||||
} catch {
|
||||
setSaveMessage({ type: 'error', text: 'Failed to add break period' })
|
||||
} catch (err) {
|
||||
setSaveMessage({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : 'Failed to add break period',
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRemoveBreakPeriod = async (id: string) => {
|
||||
try {
|
||||
setIsSaving(true)
|
||||
await removeBreakPeriod(id)
|
||||
setSaveMessage({ type: 'success', text: 'Break period removed!' })
|
||||
setTimeout(() => setSaveMessage(null), 3000)
|
||||
} catch (err) {
|
||||
setSaveMessage({
|
||||
type: 'error',
|
||||
text: err instanceof Error ? err.message : 'Failed to remove break period',
|
||||
})
|
||||
} finally {
|
||||
setIsSaving(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Layout header={{ title: 'Inställningar' }}>
|
||||
<Layout>
|
||||
<div data-testid="settings-page" className="settings-page">
|
||||
<h1>Settings</h1>
|
||||
|
||||
@@ -79,10 +180,23 @@ export function Settings(): ReactNode {
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>Child Information</h2>
|
||||
<ChildBirthdayInput
|
||||
birthDate={childBirthDate}
|
||||
onBirthDateChange={setChildBirthDate}
|
||||
/>
|
||||
{selectedChild && (
|
||||
<>
|
||||
<p>Selected child: <strong>{selectedChild.name}</strong></p>
|
||||
<ChildBirthdayInput
|
||||
birthDate={childBirthDate}
|
||||
onBirthDateChange={setChildBirthDate}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSaveChildBirthDate}
|
||||
className="settings-page__button"
|
||||
disabled={isSaving}
|
||||
data-testid="save-child-button"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Child Birth Date'}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
@@ -91,14 +205,30 @@ export function Settings(): ReactNode {
|
||||
parent={parentData}
|
||||
onChange={(field, val) => setParentData({ ...parentData, [field]: val })}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSaveParentProfile}
|
||||
className="settings-page__button"
|
||||
disabled={isSaving}
|
||||
data-testid="save-parent-button"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Parent Profile'}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
<h2>Workspace Settings</h2>
|
||||
<PlanningGoalSelector
|
||||
value={planningGoal}
|
||||
onChange={(goal) => setPlanningGoal(goal)}
|
||||
onChange={(goal) => setPlanningGoal(goal as 'max_time' | 'max_money' | 'balanced')}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSavePlanningGoal}
|
||||
className="settings-page__button"
|
||||
disabled={isSaving}
|
||||
data-testid="save-planning-goal-button"
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Planning Goal'}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<section className="settings-page__section">
|
||||
@@ -113,9 +243,10 @@ export function Settings(): ReactNode {
|
||||
<button
|
||||
onClick={handleSaveVacationDays}
|
||||
className="settings-page__button"
|
||||
disabled={isSaving}
|
||||
data-testid="save-vacation-button"
|
||||
>
|
||||
Save Vacation Days
|
||||
{isSaving ? 'Saving...' : 'Save Vacation Days'}
|
||||
</button>
|
||||
</section>
|
||||
|
||||
@@ -124,7 +255,7 @@ export function Settings(): ReactNode {
|
||||
<BreakPeriodsInput
|
||||
periods={breakPeriods ?? []}
|
||||
onAdd={handleAddBreakPeriod}
|
||||
onRemove={removeBreakPeriod}
|
||||
onRemove={handleRemoveBreakPeriod}
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user