Files
accounted/components/settings/VoucherSeriesManager.tsx
T
Mattsson 973a2b81a0 Voucher series switcher (#219)
* Implement company and account deletion features

- Add event types for company and account deletion to CoreEvent.
- Enhance Supabase middleware to handle company context resolution and cookie management for archived companies.
- Create API routes for deleting accounts and companies, including necessary validations and event emissions.
- Implement tests for account and company deletion endpoints to ensure proper functionality and error handling.
- Add retention notice component to inform users about bookkeeping data retention during destructive actions.
- Create database migrations to support soft deletion of companies and anonymization of user accounts, ensuring compliance with retention laws.

* feat: enhance account deletion process and update user notifications

* Add service client for onboarding completion check and update escape hatch visibility

* Enhance invite flow and email handling for company members

* Refactor company context and RLS policies for active company isolation

- Update `switchCompany` to remove unnecessary revalidation as client handles navigation.
- Revise `getActiveCompanyId` to prioritize `user_preferences` and validate against non-archived memberships.
- Modify `setActiveCompany` to ensure `user_preferences` is the authoritative source while maintaining cookie compatibility.
- Enhance middleware to resolve active company using `user_preferences` and fallback to first non-archived membership.
- Introduce new API route `/api/company/current` to fetch the active company ID for cross-tab synchronization.
- Implement `CompanyTabSync` component for real-time active company enforcement across tabs.
- Create migration for RLS policies to enforce single-active-company isolation using `current_active_company_id()`.

* feat: implement viewer role enforcement for write permissions

- Added `useCanWrite` hook to determine if the current user has write permissions based on their role in the active company.
- Updated various components (JournalEntryForm, CustomerForm, DeadlineForm, etc.) to disable write actions and show a lock icon with a tooltip for users without write permissions.
- Introduced `requireWritePermission` function to enforce write permissions at the API level, returning a 403 response for viewers.
- Created tests to verify the behavior of the viewer role and write permissions.
- Added database migration to enforce read-only access for viewers at the database level.

* Add default voucher series configuration for manual journal entries
2026-04-12 23:39:14 +02:00

89 lines
3.1 KiB
TypeScript

'use client'
import { useState, useEffect, useCallback } from 'react'
import { createClient } from '@/lib/supabase/client'
import { useCompany } from '@/contexts/CompanyContext'
import { Label } from '@/components/ui/label'
import { Badge } from '@/components/ui/badge'
interface VoucherSeries {
voucher_series: string
last_number: number
fiscal_period_id: string
}
interface VoucherSeriesManagerProps {
defaultSeries?: string
}
export function VoucherSeriesManager({ defaultSeries }: VoucherSeriesManagerProps) {
const { company } = useCompany()
const [series, setSeries] = useState<VoucherSeries[]>([])
const [isLoading, setIsLoading] = useState(true)
const fetchSeries = useCallback(async () => {
if (!company?.id) return
const supabase = createClient()
const { data } = await supabase
.from('voucher_sequences')
.select('voucher_series, last_number, fiscal_period_id')
.eq('company_id', company.id)
.order('voucher_series')
setSeries(data || [])
setIsLoading(false)
}, [company?.id])
useEffect(() => { fetchSeries() }, [fetchSeries])
// Group by series letter, show the highest last_number
const grouped = series.reduce<Record<string, number>>((acc, s) => {
const existing = acc[s.voucher_series] || 0
acc[s.voucher_series] = Math.max(existing, s.last_number)
return acc
}, {})
const seriesEntries = Object.entries(grouped).sort(([a], [b]) => a.localeCompare(b))
return (
<section className="space-y-4">
<h2 className="text-sm font-medium uppercase tracking-wider text-muted-foreground">
Verifikationsserier
</h2>
{isLoading ? (
<div className="space-y-2">
<div className="h-4 bg-muted rounded w-32 animate-pulse" />
<div className="h-4 bg-muted rounded w-24 animate-pulse" />
</div>
) : seriesEntries.length === 0 ? (
<p className="text-sm text-muted-foreground">
Inga verifikationsserier ännu. Serie {defaultSeries || 'A'} skapas automatiskt vid första verifikationen.
</p>
) : (
<div className="space-y-2">
<Label className="text-xs text-muted-foreground">Aktiva serier</Label>
<div className="divide-y divide-border/8">
{seriesEntries.map(([letter, lastNum]) => (
<div key={letter} className="flex items-center justify-between py-2">
<div className="flex items-center gap-2">
<span className="text-sm font-medium tabular-nums">Serie {letter}</span>
{letter === (defaultSeries || 'A') && (
<Badge variant="outline" className="text-[10px] px-1.5 py-0">standard</Badge>
)}
</div>
<span className="text-sm text-muted-foreground tabular-nums">
Senaste nr: {lastNum}
</span>
</div>
))}
</div>
</div>
)}
<p className="text-xs text-muted-foreground">
Nya serier skapas automatiskt första gången de används vid bokföring.
</p>
</section>
)
}