feat: dynamic voucher series dropdown in SIE import (#274)
* feat: dynamic voucher series dropdown in SIE import
Populate the voucher series picker on the import review step from the
company's own data instead of a hardcoded A/B/C/I list. Shows all A–Z
series with inline labels for the company default ("standard") and any
series that already have a running sequence ("används redan"), and
preselects the company default (falling back to B, then the first
existing series, then A).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: address voucher series dropdown review feedback
- Log non-PGRST116 Supabase errors from company_settings and
voucher_sequences fetches instead of silently swallowing them.
- Disable the series Select until the async load completes so users
don't briefly see the 'B' fallback before it snaps to the real
company default.
- Replace the seriesInitializedRef guard with a seriesLoaded state that
resets on company.id change, so switching companies re-runs the
preselection instead of sticking with the prior company's choice.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,8 +25,12 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { useUnsavedChanges } from '@/lib/hooks/use-unsaved-changes'
|
||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import type { ImportPreview, AccountMapping } from '@/lib/import/types'
|
||||
|
||||
const SERIES_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||
|
||||
interface ImportReviewStepProps {
|
||||
preview: ImportPreview
|
||||
mappings: AccountMapping[]
|
||||
@@ -50,15 +54,67 @@ export default function ImportReviewStep({
|
||||
isLoading,
|
||||
}: ImportReviewStepProps) {
|
||||
const { canWrite } = useCanWrite()
|
||||
const { company } = useCompany()
|
||||
const [options, setOptions] = useState<ImportExecuteOptions>({
|
||||
createFiscalPeriod: true,
|
||||
importOpeningBalances: true,
|
||||
importTransactions: true,
|
||||
voucherSeries: 'B',
|
||||
})
|
||||
const [defaultSeries, setDefaultSeries] = useState<string | null>(null)
|
||||
const [existingSeries, setExistingSeries] = useState<Set<string>>(new Set())
|
||||
const [seriesLoaded, setSeriesLoaded] = useState(false)
|
||||
const [elapsed, setElapsed] = useState(0)
|
||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!company?.id) return
|
||||
setSeriesLoaded(false)
|
||||
const supabase = createClient()
|
||||
|
||||
let cancelled = false
|
||||
;(async () => {
|
||||
const [
|
||||
{ data: settingsData, error: settingsError },
|
||||
{ data: sequencesData, error: sequencesError },
|
||||
] = await Promise.all([
|
||||
supabase
|
||||
.from('company_settings')
|
||||
.select('default_voucher_series')
|
||||
.eq('company_id', company.id)
|
||||
.single(),
|
||||
supabase
|
||||
.from('voucher_sequences')
|
||||
.select('voucher_series')
|
||||
.eq('company_id', company.id),
|
||||
])
|
||||
|
||||
if (cancelled) return
|
||||
|
||||
// PGRST116 = no rows returned from .single(); expected when settings not yet created.
|
||||
if (settingsError && settingsError.code !== 'PGRST116') {
|
||||
console.error('Failed to load company settings for voucher series', settingsError)
|
||||
}
|
||||
if (sequencesError) {
|
||||
console.error('Failed to load voucher sequences', sequencesError)
|
||||
}
|
||||
|
||||
const companyDefault = settingsData?.default_voucher_series || null
|
||||
const sequences = new Set<string>((sequencesData || []).map((row) => row.voucher_series))
|
||||
|
||||
setDefaultSeries(companyDefault)
|
||||
setExistingSeries(sequences)
|
||||
|
||||
const initial = companyDefault || (sequences.has('B') ? 'B' : Array.from(sequences).sort()[0]) || 'A'
|
||||
setOptions((prev) => ({ ...prev, voucherSeries: initial }))
|
||||
setSeriesLoaded(true)
|
||||
})()
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [company?.id])
|
||||
|
||||
// Block browser close/refresh during import
|
||||
useUnsavedChanges(isLoading)
|
||||
|
||||
@@ -240,15 +296,26 @@ export default function ImportReviewStep({
|
||||
<Select
|
||||
value={options.voucherSeries}
|
||||
onValueChange={(value) => updateOption('voucherSeries', value)}
|
||||
disabled={!seriesLoaded}
|
||||
>
|
||||
<SelectTrigger id="voucher-series" className="w-48">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="A">A - Huvudserie</SelectItem>
|
||||
<SelectItem value="B">B - Import</SelectItem>
|
||||
<SelectItem value="C">C - Korrigeringar</SelectItem>
|
||||
<SelectItem value="I">I - Import (alternativ)</SelectItem>
|
||||
{SERIES_LETTERS.map((letter) => {
|
||||
const isDefault = defaultSeries === letter
|
||||
const isExisting = existingSeries.has(letter)
|
||||
const suffix = isDefault
|
||||
? ' — standard'
|
||||
: isExisting
|
||||
? ' — används redan'
|
||||
: ''
|
||||
return (
|
||||
<SelectItem key={letter} value={letter}>
|
||||
{`Serie ${letter}${suffix}`}
|
||||
</SelectItem>
|
||||
)
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
|
||||
Reference in New Issue
Block a user