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:
co-authored by
Claude Opus 4.7
parent
b5fc95276c
commit
2ea5a72b3d
@@ -25,8 +25,12 @@ import {
|
|||||||
} from 'lucide-react'
|
} from 'lucide-react'
|
||||||
import { useUnsavedChanges } from '@/lib/hooks/use-unsaved-changes'
|
import { useUnsavedChanges } from '@/lib/hooks/use-unsaved-changes'
|
||||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
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'
|
import type { ImportPreview, AccountMapping } from '@/lib/import/types'
|
||||||
|
|
||||||
|
const SERIES_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
|
||||||
|
|
||||||
interface ImportReviewStepProps {
|
interface ImportReviewStepProps {
|
||||||
preview: ImportPreview
|
preview: ImportPreview
|
||||||
mappings: AccountMapping[]
|
mappings: AccountMapping[]
|
||||||
@@ -50,15 +54,67 @@ export default function ImportReviewStep({
|
|||||||
isLoading,
|
isLoading,
|
||||||
}: ImportReviewStepProps) {
|
}: ImportReviewStepProps) {
|
||||||
const { canWrite } = useCanWrite()
|
const { canWrite } = useCanWrite()
|
||||||
|
const { company } = useCompany()
|
||||||
const [options, setOptions] = useState<ImportExecuteOptions>({
|
const [options, setOptions] = useState<ImportExecuteOptions>({
|
||||||
createFiscalPeriod: true,
|
createFiscalPeriod: true,
|
||||||
importOpeningBalances: true,
|
importOpeningBalances: true,
|
||||||
importTransactions: true,
|
importTransactions: true,
|
||||||
voucherSeries: 'B',
|
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 [elapsed, setElapsed] = useState(0)
|
||||||
const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
|
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
|
// Block browser close/refresh during import
|
||||||
useUnsavedChanges(isLoading)
|
useUnsavedChanges(isLoading)
|
||||||
|
|
||||||
@@ -240,15 +296,26 @@ export default function ImportReviewStep({
|
|||||||
<Select
|
<Select
|
||||||
value={options.voucherSeries}
|
value={options.voucherSeries}
|
||||||
onValueChange={(value) => updateOption('voucherSeries', value)}
|
onValueChange={(value) => updateOption('voucherSeries', value)}
|
||||||
|
disabled={!seriesLoaded}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="voucher-series" className="w-48">
|
<SelectTrigger id="voucher-series" className="w-48">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="A">A - Huvudserie</SelectItem>
|
{SERIES_LETTERS.map((letter) => {
|
||||||
<SelectItem value="B">B - Import</SelectItem>
|
const isDefault = defaultSeries === letter
|
||||||
<SelectItem value="C">C - Korrigeringar</SelectItem>
|
const isExisting = existingSeries.has(letter)
|
||||||
<SelectItem value="I">I - Import (alternativ)</SelectItem>
|
const suffix = isDefault
|
||||||
|
? ' — standard'
|
||||||
|
: isExisting
|
||||||
|
? ' — används redan'
|
||||||
|
: ''
|
||||||
|
return (
|
||||||
|
<SelectItem key={letter} value={letter}>
|
||||||
|
{`Serie ${letter}${suffix}`}
|
||||||
|
</SelectItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
|
|||||||
Reference in New Issue
Block a user