diff --git a/components/bookkeeping/FiscalPeriodDateFields.tsx b/components/bookkeeping/FiscalPeriodDateFields.tsx new file mode 100644 index 00000000..848a0583 --- /dev/null +++ b/components/bookkeeping/FiscalPeriodDateFields.tsx @@ -0,0 +1,180 @@ +'use client' + +import { useMemo, type ReactNode } from 'react' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { CalendarDays } from 'lucide-react' +import { + monthsBetween, + parseDateParts, + validatePeriodDuration, +} from '@/lib/bookkeeping/validate-period-duration' +import type { EntityType } from '@/types' + +const monthNames = [ + 'januari', 'februari', 'mars', 'april', 'maj', 'juni', + 'juli', 'augusti', 'september', 'oktober', 'november', 'december', +] + +function formatSwedishDate(dateStr: string): string { + const { year, month, day } = parseDateParts(dateStr) + return `${day} ${monthNames[month - 1]} ${year}` +} + +function endsOnDec31(end: string): boolean { + const e = parseDateParts(end) + return e.month === 12 && e.day === 31 +} + +/** + * Map validatePeriodDuration's English messages to user-facing Swedish copy. + */ +function toSwedishError(msg: string): string { + if (msg.includes('after period start')) return 'Slutdatum måste vara efter startdatum.' + if (msg.includes('1st of a month')) return 'Startdatum måste vara den första i månaden.' + if (msg.includes('last day of a month')) return 'Slutdatum måste vara den sista i månaden.' + if (msg.includes('exceeds maximum 18 months')) return 'Räkenskapsåret får vara högst 18 månader (BFL 3 kap.).' + if (msg.includes('at least 6 months')) return 'Första räkenskapsåret måste vara minst 6 månader (BFL 3 kap.).' + return msg +} + +export interface FiscalPeriodValidation { + /** User-facing Swedish error, or null if valid */ + error: string | null + /** Integer month count, or null if inputs are incomplete/invalid */ + months: number | null + /** True if inputs are complete enough to render the summary */ + canSummarise: boolean +} + +/** + * Shared validation for the first fiscal period — used by both onboarding Step 3 + * and the settings FiscalPeriodEditor. Returns Swedish error copy. + */ +export function validateFirstPeriod( + startDate: string, + endDate: string, + entityType: EntityType | undefined +): FiscalPeriodValidation { + if (!startDate || !endDate) { + return { error: null, months: null, canSummarise: false } + } + if (endDate <= startDate) { + return { + error: 'Slutdatum måste vara efter startdatum.', + months: null, + canSummarise: false, + } + } + + const baseError = validatePeriodDuration(startDate, endDate, { isFirstPeriod: true }) + if (baseError) { + return { + error: toSwedishError(baseError), + months: monthsBetween(startDate, endDate), + canSummarise: true, + } + } + + if (entityType === 'enskild_firma' && !endsOnDec31(endDate)) { + return { + error: 'Enskild firma måste ha slutdatum 31 december (BFL 3 kap.).', + months: monthsBetween(startDate, endDate), + canSummarise: true, + } + } + + return { + error: null, + months: monthsBetween(startDate, endDate), + canSummarise: true, + } +} + +interface FiscalPeriodDateFieldsProps { + startDate: string + onStartDateChange: (value: string) => void + startHelpText?: string + /** + * Render the end-date control. Onboarding passes its AB end-month ; settings passes a native . + */ + endDateSlot: ReactNode + /** The raw end-date string (used for summary + validation). */ + endDate: string + entityType: EntityType | undefined + /** Label for the summary card. Defaults to "Ditt första räkenskapsår". */ + summaryTitle?: string + /** Optional override for start date