Fix/company creation bug (#212)

* feat: enhance JournalEntryForm with currency selection and exchange rate fetching

- Added currency selection to JournalEntryForm, allowing users to choose from multiple currencies (SEK, EUR, USD, GBP, NOK, DKK).
- Implemented fetching of exchange rates from Riksbanken API based on selected currency and entry date.
- Updated calculations for foreign amounts and SEK equivalents based on user input and fetched exchange rates.
- Improved form handling to reset currency-related fields when switching back to SEK.

feat: refactor WelcomeOnboarding to streamline company creation process

- Replaced direct company switching with a new server action to create a company from onboarding data.
- Added validation for fiscal period during onboarding steps, allowing for mid-month starts for the first fiscal period.
- Enhanced error handling and rollback mechanisms to ensure data integrity during company creation.

fix: update Step3TaxRegistration to allow flexible first-year start dates

- Modified date selection to include day, month, and year for the first-year start date.
- Updated validation messages to reflect changes in fiscal year start date handling.

test: expand validate-period-duration tests for fiscal period validation

- Added tests to validate that mid-month starts are allowed for the first fiscal period.
- Ensured that subsequent periods must start on the 1st of the month and enforced maximum duration constraints.

feat: implement currency rate API endpoint

- Created a new API route to fetch exchange rates for specified currencies, ensuring user authentication.
- Validated currency input and handled errors for invalid requests.

chore: update database constraints for fiscal periods

- Modified database constraints to allow custom start dates for the first fiscal period while enforcing day-1 starts for subsequent periods.

* fix: implement computeFiscalPeriod function for onboarding and refactor JournalEntryForm

* Fixed date issue

* Added migration
This commit is contained in:
Mattsson
2026-04-10 11:02:28 +02:00
committed by GitHub
parent e8928b7885
commit aa405b9a74
14 changed files with 664 additions and 555 deletions
+150 -12
View File
@@ -1,13 +1,13 @@
'use client'
import { useState, useEffect } from 'react'
import { useState, useEffect, useCallback } from 'react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { useToast } from '@/components/ui/use-toast'
import { Plus, Trash2, AlertTriangle } from 'lucide-react'
import { Plus, Trash2, AlertTriangle, Loader2 } from 'lucide-react'
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
import { JournalEntryReviewContent } from '@/components/bookkeeping/JournalEntryReviewContent'
import DocumentUploadZone from '@/components/bookkeeping/DocumentUploadZone'
@@ -16,7 +16,16 @@ import { getErrorMessage } from '@/lib/errors/get-error-message'
import { formatCurrency } from '@/lib/utils'
import { useUnsavedChanges } from '@/lib/hooks/use-unsaved-changes'
import type { UploadedFile } from '@/components/bookkeeping/DocumentUploadZone'
import type { CreateJournalEntryLineInput, FiscalPeriod, BASAccount, JournalEntrySourceType } from '@/types'
import type { CreateJournalEntryLineInput, FiscalPeriod, BASAccount, JournalEntrySourceType, Currency } from '@/types'
const CURRENCIES: { value: Currency; label: string }[] = [
{ value: 'SEK', label: 'SEK' },
{ value: 'EUR', label: 'EUR' },
{ value: 'USD', label: 'USD' },
{ value: 'GBP', label: 'GBP' },
{ value: 'NOK', label: 'NOK' },
{ value: 'DKK', label: 'DKK' },
]
export interface FormLine {
account_number: string
@@ -66,6 +75,12 @@ export default function JournalEntryForm({
const [showNoDocWarning, setShowNoDocWarning] = useState(false)
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
const [accounts, setAccounts] = useState<BASAccount[]>([])
const [entryCurrency, setEntryCurrency] = useState<Currency>('SEK')
const [exchangeRate, setExchangeRate] = useState('')
const [isFetchingRate, setIsFetchingRate] = useState(false)
const [foreignAmount, setForeignAmount] = useState('')
const isForeign = entryCurrency !== 'SEK'
const isUploading = uploadedFiles.some((f) => f.status === 'uploading')
@@ -94,6 +109,31 @@ export default function JournalEntryForm({
fetchAccounts()
}, [])
// Fetch exchange rate from Riksbanken when currency changes
const fetchRate = useCallback(async (currency: Currency) => {
if (currency === 'SEK') return
setIsFetchingRate(true)
try {
const res = await fetch(`/api/currency/rate?currency=${currency}&date=${entryDate}`)
if (res.ok) {
const { data } = await res.json()
if (data?.rate) {
setExchangeRate(String(data.rate))
}
}
} catch {
// Non-critical — user can enter rate manually
} finally {
setIsFetchingRate(false)
}
}, [entryDate])
useEffect(() => {
if (entryCurrency !== 'SEK') {
fetchRate(entryCurrency)
}
}, [entryCurrency, fetchRate])
const addLine = () => {
setLines([...lines, { ...BLANK_LINE }])
}
@@ -142,6 +182,19 @@ export default function JournalEntryForm({
const totalCredit = lines.reduce((sum, l) => sum + (parseFloat(l.credit_amount) || 0), 0)
const isBalanced = Math.round((totalDebit - totalCredit) * 100) === 0 && totalDebit > 0
const rate = parseFloat(exchangeRate) || 0
// If user has manually entered a foreign amount, use that; otherwise derive from SEK total
const parsedForeignInput = parseFloat(foreignAmount) || 0
const computedForeignAmount = isForeign && rate > 0
? (parsedForeignInput > 0
? parsedForeignInput
: (totalDebit > 0 ? Math.round(totalDebit / rate * 100) / 100 : 0))
: 0
// The expected SEK equivalent based on foreign amount × rate
const computedSekAmount = isForeign && rate > 0 && computedForeignAmount > 0
? Math.round(computedForeignAmount * rate * 100) / 100
: 0
const handleReview = () => {
if (!selectedPeriod || !description || !isBalanced) return
const hasDocuments = uploadedFiles.some((f) => f.status === 'uploaded')
@@ -155,17 +208,34 @@ export default function JournalEntryForm({
const handleConfirm = async () => {
setIsSubmitting(true)
let currencyMetaApplied = false
const entryLines: CreateJournalEntryLineInput[] = lines
.filter((l) => l.account_number && (l.debit_amount || l.credit_amount))
.map((l) => ({
account_number: l.account_number,
debit_amount: parseFloat(l.debit_amount) || 0,
credit_amount: parseFloat(l.credit_amount) || 0,
line_description: l.line_description || undefined,
...(l.currency ? { currency: l.currency } : {}),
...(l.amount_in_currency != null ? { amount_in_currency: l.amount_in_currency } : {}),
...(l.exchange_rate != null ? { exchange_rate: l.exchange_rate } : {}),
}))
.map((l) => {
const base: CreateJournalEntryLineInput = {
account_number: l.account_number,
debit_amount: parseFloat(l.debit_amount) || 0,
credit_amount: parseFloat(l.credit_amount) || 0,
line_description: l.line_description || undefined,
}
// Attach currency metadata from pre-populated data (e.g. transaction flow)
if (l.currency) {
base.currency = l.currency
if (l.amount_in_currency != null) base.amount_in_currency = l.amount_in_currency
if (l.exchange_rate != null) base.exchange_rate = l.exchange_rate
}
// Attach currency metadata from the entry-level currency selector (manual flow)
// Applied to the first bank/cash account (class 19xx) only
else if (isForeign && rate > 0 && l.account_number.startsWith('19') && !currencyMetaApplied) {
base.currency = entryCurrency
base.amount_in_currency = computedForeignAmount
base.exchange_rate = rate
currencyMetaApplied = true
}
return base
})
const url = submitUrl ?? '/api/bookkeeping/journal-entries'
@@ -225,6 +295,9 @@ export default function JournalEntryForm({
setDescription('')
setUploadedFiles([])
setLines([{ ...BLANK_LINE }, { ...BLANK_LINE }])
setEntryCurrency('SEK')
setExchangeRate('')
setForeignAmount('')
onCreated?.()
if (journalEntryId) {
onEntryCreated?.(journalEntryId)
@@ -272,6 +345,71 @@ export default function JournalEntryForm({
</div>
</div>
{/* Currency section */}
<div className="flex flex-wrap items-end gap-3">
<div className="w-24">
<Label className="text-xs text-muted-foreground">Valuta</Label>
<Select value={entryCurrency} onValueChange={(v) => {
setEntryCurrency(v as Currency)
if (v === 'SEK') {
setExchangeRate('')
setForeignAmount('')
}
}}>
<SelectTrigger className="mt-1 h-8">
<SelectValue />
</SelectTrigger>
<SelectContent>
{CURRENCIES.map((c) => (
<SelectItem key={c.value} value={c.value}>{c.label}</SelectItem>
))}
</SelectContent>
</Select>
</div>
{isForeign && (
<>
<div className="w-40">
<Label className="text-xs text-muted-foreground">
Omräkningskurs (1 {entryCurrency} = ? SEK)
</Label>
<div className="relative mt-1">
<Input
type="number"
value={exchangeRate}
onChange={(e) => setExchangeRate(e.target.value)}
placeholder="0,0000"
className="h-8 pr-8"
step="0.0001"
min="0"
/>
{isFetchingRate && (
<Loader2 className="absolute right-2 top-1.5 h-4 w-4 animate-spin text-muted-foreground" />
)}
</div>
</div>
<div className="w-40">
<Label className="text-xs text-muted-foreground">
Belopp i {entryCurrency}
</Label>
<Input
type="number"
value={foreignAmount || (computedForeignAmount > 0 && !parsedForeignInput ? computedForeignAmount.toFixed(2) : '')}
onChange={(e) => setForeignAmount(e.target.value)}
placeholder="0,00"
className="mt-1 h-8"
step="0.01"
min="0"
/>
</div>
{rate > 0 && computedForeignAmount > 0 && (
<p className="text-xs text-muted-foreground pb-1">
{computedForeignAmount.toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {entryCurrency} × {rate.toLocaleString('sv-SE', { minimumFractionDigits: 4 })} = {computedSekAmount.toLocaleString('sv-SE', { minimumFractionDigits: 2 })} SEK
</p>
)}
</>
)}
</div>
{/* Entry lines — mobile cards */}
<div className="sm:hidden space-y-3">
{lines.map((line, index) => (
+57 -267
View File
@@ -4,11 +4,11 @@ import { useState, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import * as Sentry from '@sentry/nextjs'
import { createClient } from '@/lib/supabase/client'
import { switchCompany } from '@/lib/company/actions'
import { createCompanyFromOnboarding } from '@/lib/company/actions'
import { computeFiscalPeriod } from '@/lib/company/compute-fiscal-period'
import { useToast } from '@/components/ui/use-toast'
import { Loader2, Building2, Plus } from 'lucide-react'
import { cn } from '@/lib/utils'
import { validatePeriodDuration } from '@/lib/bookkeeping/validate-period-duration'
import { ENABLED_EXTENSION_IDS } from '@/lib/extensions/_generated/enabled-extensions'
import type { CompanyLookupResult, EnrichmentCompanyRole } from '@/lib/company-lookup/types'
import type { CompanySettings, EntityType, MomsPeriod } from '@/types'
@@ -73,7 +73,6 @@ export default function WelcomeOnboarding({ firstName, teamId, skipWelcome, hasE
const [isSaving, setIsSaving] = useState(false)
const [currentStep, setCurrentStep] = useState(1)
const [settings, setSettings] = useState<Partial<CompanySettings>>({})
const [companyId, setCompanyId] = useState<string | null>(null)
const ticEnabled = ENABLED_EXTENSION_IDS.has('tic')
const [ticLookup, setTicLookup] = useState<CompanyLookupResult | null>(null)
const [enrichmentCompanies, setEnrichmentCompanies] = useState<EnrichmentCompanyRole[]>([])
@@ -138,291 +137,82 @@ export default function WelcomeOnboarding({ firstName, teamId, skipWelcome, hasE
loadEnrichment()
}, [supabase, router])
const saveSettings = async (updates: Partial<CompanySettings>, nextStep?: number) => {
const targetStep = nextStep ?? currentStep
setIsSaving(true)
try {
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
router.push('/login')
return false
}
const updatedSettings = {
...settings,
...updates,
onboarding_step: targetStep,
}
if (!companyId) {
logError('save aborted: no companyId', { step: targetStep })
return false
}
const {
id: _id, user_id: _uid, company_id: _cid, created_at: _ca, updated_at: _ua,
is_first_fiscal_year: _ify, first_year_start: _fys, first_year_end: _fye,
...settingsToSave
} = updatedSettings as Record<string, unknown>
const { error } = await supabase
.from('company_settings')
.upsert({ ...settingsToSave, company_id: companyId }, { onConflict: 'company_id' })
if (error) {
logError('save failed', { message: error.message, step: targetStep, code: error.code })
toast({ title: 'Fel', description: error.message || 'Kunde inte spara. Försök igen.', variant: 'destructive' })
return false
}
setSettings(updatedSettings)
return true
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
logError('saveSettings threw', { message, step: targetStep })
Sentry.captureException(err)
toast({ title: 'Fel', description: 'Ett oväntat fel uppstod. Försök igen.', variant: 'destructive' })
return false
} finally {
setIsSaving(false)
}
}
const handleNext = async (stepData: Partial<CompanySettings>) => {
if (currentStep === 1 && stepData.entity_type && stepData.entity_type !== settings.entity_type) {
stepData = { ...stepData, org_number: '', company_name: '' }
setTicLookup(null)
}
let activeCompanyId = companyId
const mergedSettings = { ...settings, ...stepData }
if (currentStep === 1 && !activeCompanyId) {
try {
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
router.push('/login')
return
}
const { data: newCompanyId, error: rpcError } = await supabase.rpc('create_company_with_owner', {
p_name: 'Mitt företag',
p_entity_type: stepData.entity_type,
p_team_id: teamId,
})
if (rpcError || !newCompanyId) {
logError('company creation failed', { message: rpcError?.message, code: rpcError?.code })
toast({ title: 'Fel', description: 'Kunde inte skapa företag. Försök igen.', variant: 'destructive' })
return
}
activeCompanyId = newCompanyId
setCompanyId(activeCompanyId)
console.log(LOG, 'created company', activeCompanyId)
} catch (err) {
logError('company creation threw', { error: String(err) })
Sentry.captureException(err)
toast({ title: 'Fel', description: 'Kunde inte skapa företag. Försök igen.', variant: 'destructive' })
return
}
}
if (!activeCompanyId) {
logError('handleNext aborted: no companyId', { step: currentStep })
return
}
const nextStep = currentStep + 1
const needsDirectSave = currentStep === 1 && !companyId
const success = needsDirectSave
? await (async () => {
setIsSaving(true)
try {
const updatedSettings = { ...settings, ...stepData, onboarding_step: nextStep }
const {
id: _id, user_id: _uid, company_id: _cid, created_at: _ca, updated_at: _ua,
is_first_fiscal_year: _ify, first_year_start: _fys, first_year_end: _fye,
...settingsToSave
} = updatedSettings as Record<string, unknown>
const { error } = await supabase
.from('company_settings')
.upsert({ ...settingsToSave, company_id: activeCompanyId }, { onConflict: 'company_id' })
if (error) {
logError('save failed', { message: error.message, step: nextStep, code: error.code })
toast({ title: 'Fel', description: error.message || 'Kunde inte spara. Försök igen.', variant: 'destructive' })
return false
}
setSettings(updatedSettings)
return true
} catch (err) {
logError('saveSettings threw', { message: String(err), step: nextStep })
Sentry.captureException(err)
return false
} finally {
setIsSaving(false)
}
})()
: await saveSettings(stepData, nextStep)
if (!success) {
logError('handleNext aborted: saveSettings failed', { step: currentStep })
return
}
// After step 2: sync company name to companies table
if (currentStep === 2 && stepData.company_name && activeCompanyId) {
const { error: nameError } = await supabase
.from('companies')
.update({ name: stepData.company_name })
.eq('id', activeCompanyId)
if (nameError) {
logError('failed to sync company name', { message: nameError.message })
}
}
// After step 1: seed chart of accounts
if (currentStep === 1 && stepData.entity_type) {
try {
const { error: rpcError } = await supabase.rpc('seed_chart_of_accounts', {
p_company_id: activeCompanyId,
p_entity_type: stepData.entity_type,
})
if (rpcError) {
logError('COA seeding failed', { entity_type: stepData.entity_type, message: rpcError.message })
}
} catch (err) {
logError('COA seeding threw', { error: String(err) })
Sentry.captureException(err)
}
}
// After step 3: create fiscal period
if (currentStep === 3 && activeCompanyId) {
try {
const isFirstYear = stepData.is_first_fiscal_year as boolean | undefined
const firstYearStart = stepData.first_year_start as string | undefined
const firstYearEnd = stepData.first_year_end as string | undefined
let startStr: string
let endStr: string
let periodName: string
if (isFirstYear && firstYearStart && firstYearEnd) {
startStr = firstYearStart
endStr = firstYearEnd
const startYear = new Date(firstYearStart).getFullYear()
const endYear = new Date(firstYearEnd).getFullYear()
periodName = startYear === endYear
? `Första räkenskapsåret ${startYear}`
: `Första räkenskapsåret ${startYear}/${endYear}`
} else {
let startMonth = stepData.fiscal_year_start_month || settings.fiscal_year_start_month || 1
if (settings.entity_type === 'enskild_firma') startMonth = 1
const currentYear = new Date().getFullYear()
startStr = `${currentYear}-${String(startMonth).padStart(2, '0')}-01`
let endYear: number
let endMonth: number
if (startMonth === 1) {
endYear = currentYear
endMonth = 12
} else {
endYear = currentYear + 1
endMonth = startMonth - 1
}
const lastDay = new Date(endYear, endMonth, 0).getDate()
endStr = `${endYear}-${String(endMonth).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`
periodName = startMonth === 1
? `Räkenskapsår ${currentYear}`
: `Räkenskapsår ${currentYear}/${currentYear + 1}`
}
const validationError = validatePeriodDuration(startStr, endStr)
if (validationError) {
logError('fiscal period validation failed', { validationError, startStr, endStr })
toast({
title: 'Ogiltigt räkenskapsår',
description: translatePeriodError(validationError),
variant: 'destructive',
})
setCurrentStep(3)
return
}
// Clean up empty fiscal periods
const { data: existingPeriods } = await supabase
.from('fiscal_periods')
.select('id')
.eq('company_id', activeCompanyId)
if (existingPeriods && existingPeriods.length > 0) {
for (const ep of existingPeriods) {
const { count } = await supabase
.from('journal_entries')
.select('id', { count: 'exact', head: true })
.eq('fiscal_period_id', ep.id)
if (count === 0) {
await supabase.from('fiscal_periods').delete().eq('id', ep.id)
}
}
}
const { error: upsertError } = await supabase.from('fiscal_periods').upsert({
company_id: activeCompanyId,
name: periodName,
period_start: startStr,
period_end: endStr,
}, { onConflict: 'company_id,period_start,period_end' })
if (upsertError) {
logError('fiscal period upsert failed', { message: upsertError.message, startStr, endStr })
}
} catch (err) {
logError('fiscal period creation threw', { error: String(err) })
Sentry.captureException(err)
// Validate fiscal period at step 3 before advancing
if (currentStep === 3) {
const periodResult = computeFiscalPeriod(mergedSettings)
if (periodResult.error) {
toast({
title: 'Kunde inte skapa räkenskapsår',
description: 'Ett fel uppstod när räkenskapsåret skulle skapas. Försök igen.',
title: 'Ogiltigt räkenskapsår',
description: translatePeriodError(periodResult.error),
variant: 'destructive',
})
return
}
}
if (nextStep > totalSteps) {
const finalSuccess = await saveSettings({ onboarding_complete: true }, totalSteps)
if (!finalSuccess) {
logError('failed to set onboarding_complete after all steps')
// Steps 1-3: collect data client-side only, advance step
if (currentStep < totalSteps) {
setSettings(mergedSettings)
setCurrentStep(currentStep + 1)
return
}
// Step 4 (final): create everything via server action.
// Going through a server action ensures that if the Next.js server is
// unreachable, nothing touches Supabase — no ghost companies.
const periodResult = computeFiscalPeriod(mergedSettings)
if (periodResult.error) {
toast({
title: 'Ogiltigt räkenskapsår',
description: translatePeriodError(periodResult.error),
variant: 'destructive',
})
return
}
setIsSaving(true)
try {
const result = await createCompanyFromOnboarding({
teamId,
settings: mergedSettings as Record<string, unknown>,
fiscalPeriod: {
startDate: periodResult.startStr,
endDate: periodResult.endStr,
name: periodResult.periodName,
},
})
if (result.error || !result.companyId) {
logError('create company action failed', { error: result.error })
toast({
title: 'Fel',
description: result.error || 'Kunde inte skapa företag. Försök igen.',
variant: 'destructive',
})
return
}
// Update company name
if (settings.company_name || stepData.company_name) {
await supabase
.from('companies')
.update({ name: settings.company_name || stepData.company_name })
.eq('id', activeCompanyId)
}
// Switch to the new company
await switchCompany(activeCompanyId)
console.log(LOG, 'onboarding completed')
console.log(LOG, 'onboarding completed', result.companyId)
toast({
title: 'Välkommen!',
description: 'Ditt företag är nu redo.',
})
router.push('/')
} else {
setCurrentStep(nextStep)
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
logError('create company action threw', { error: message })
Sentry.captureException(err)
toast({ title: 'Fel', description: 'Ett oväntat fel uppstod. Försök igen.', variant: 'destructive' })
} finally {
setIsSaving(false)
}
}
+51 -15
View File
@@ -174,7 +174,12 @@ export default function Step3TaxRegistration({
const firstYearEnd = watch('first_year_end')
const fiscalYearEndMonth = watch('fiscal_year_end_month')
// State for first-year start date selectors (month/year)
// State for first-year start date selectors (day/month/year)
const [startDay, setStartDay] = useState<number>(
initialData.first_year_start
? parseDateParts(initialData.first_year_start).day
: 1
)
const [startMonth, setStartMonth] = useState<number>(
initialData.first_year_start
? parseDateParts(initialData.first_year_start).month
@@ -377,22 +382,53 @@ export default function Step3TaxRegistration({
const currentYear = new Date().getFullYear()
const years = Array.from({ length: 7 }, (_, i) => currentYear - 5 + i)
const handleMonthChange = (month: number) => {
setStartMonth(month)
if (month && startYear) {
field.onChange(`${startYear}-${String(month).padStart(2, '0')}-01`)
const updateField = (day: number, month: number, year: number) => {
if (month && year) {
const maxDay = lastDayOfMonth(year, month)
const clampedDay = Math.min(day, maxDay)
field.onChange(`${year}-${String(month).padStart(2, '0')}-${String(clampedDay).padStart(2, '0')}`)
}
}
const handleYearChange = (year: number) => {
setStartYear(year)
if (startMonth && year) {
field.onChange(`${year}-${String(startMonth).padStart(2, '0')}-01`)
}
updateField(startDay, startMonth, year)
}
const handleMonthChange = (month: number) => {
setStartMonth(month)
// Clamp day if needed when month changes
if (startYear) {
const maxDay = lastDayOfMonth(startYear, month)
if (startDay > maxDay) setStartDay(maxDay)
}
updateField(startDay, month, startYear)
}
const handleDayChange = (day: number) => {
setStartDay(day)
updateField(day, startMonth, startYear)
}
const maxDays = startMonth && startYear
? lastDayOfMonth(startYear, startMonth)
: 31
return (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div className="grid grid-cols-3 gap-2">
<Select
value={startYear ? startYear.toString() : ''}
onValueChange={(v) => { if (v) handleYearChange(parseInt(v)) }}
>
<SelectTrigger>
<SelectValue placeholder="År" />
</SelectTrigger>
<SelectContent>
{years.map((y) => (
<SelectItem key={y} value={y.toString()}>{y}</SelectItem>
))}
</SelectContent>
</Select>
<Select
value={startMonth ? startMonth.toString() : ''}
onValueChange={(v) => { if (v) handleMonthChange(parseInt(v)) }}
@@ -407,15 +443,15 @@ export default function Step3TaxRegistration({
</SelectContent>
</Select>
<Select
value={startYear ? startYear.toString() : ''}
onValueChange={(v) => { if (v) handleYearChange(parseInt(v)) }}
value={startDay.toString()}
onValueChange={(v) => { if (v) handleDayChange(parseInt(v)) }}
>
<SelectTrigger>
<SelectValue placeholder="År" />
<SelectValue placeholder="Dag" />
</SelectTrigger>
<SelectContent>
{years.map((y) => (
<SelectItem key={y} value={y.toString()}>{y}</SelectItem>
{Array.from({ length: maxDays }, (_, i) => i + 1).map((d) => (
<SelectItem key={d} value={d.toString()}>{d}</SelectItem>
))}
</SelectContent>
</Select>
@@ -424,7 +460,7 @@ export default function Step3TaxRegistration({
}}
/>
<p className="text-xs text-muted-foreground">
Månaden verksamheten startade. Räkenskapsåret börjar alltid den 1:a.
Datumet företaget registrerades. Första räkenskapsåret kan börja valfri dag.
</p>
{errors.first_year_start && (
<p className="text-xs text-destructive">{errors.first_year_start.message}</p>