diff --git a/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx b/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx new file mode 100644 index 00000000..a024359a --- /dev/null +++ b/app/(dashboard)/bookkeeping/year-end/arsredovisning/page.tsx @@ -0,0 +1,358 @@ +'use client' + +import { useCallback, useEffect, useState } from 'react' +import Link from 'next/link' +import { useSearchParams } from 'next/navigation' +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Skeleton } from '@/components/ui/skeleton' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Textarea } from '@/components/ui/textarea' +import { PageHeader } from '@/components/ui/page-header' +import { ArrowLeft, FileDown, Plus, ExternalLink } from 'lucide-react' +import { useToast } from '@/components/ui/use-toast' +import type { ArsredovisningData } from '@/lib/bokslut/arsredovisning/types' +import type { SignatureRequest } from '@/lib/bokslut/arsredovisning/signature-service' + +export default function ArsredovisningPage() { + const searchParams = useSearchParams() + const periodId = searchParams.get('period') + const { toast } = useToast() + + const [data, setData] = useState(null) + const [signatures, setSignatures] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + // Editable narrative fields + const [description, setDescription] = useState('') + const [importantEvents, setImportantEvents] = useState('') + const [resultatdisposition, setResultatdisposition] = useState('') + + // Add-signer form + const [signerName, setSignerName] = useState('') + const [signerRole, setSignerRole] = useState('Styrelseledamot') + + useEffect(() => { + if (!periodId) return + let cancelled = false + Promise.all([ + fetch(`/api/bookkeeping/fiscal-periods/${periodId}/arsredovisning`).then((r) => r.json()), + fetch(`/api/bookkeeping/fiscal-periods/${periodId}/arsredovisning/signatures`).then((r) => + r.json(), + ), + ]) + .then(([arBody, sigBody]) => { + if (cancelled) return + if (arBody?.error) { + setError(arBody.error.message ?? 'Kunde inte hämta årsredovisning') + return + } + const d = arBody.data as ArsredovisningData + setData(d) + setDescription(d.forvaltningsberattelse.description) + setImportantEvents(d.forvaltningsberattelse.important_events) + setResultatdisposition(d.forvaltningsberattelse.resultatdisposition) + setSignatures((sigBody.data ?? []) as SignatureRequest[]) + }) + .catch(() => { + if (!cancelled) setError('Kunde inte hämta årsredovisning') + }) + .finally(() => { + if (!cancelled) setLoading(false) + }) + return () => { + cancelled = true + } + }, [periodId]) + + const handleAddSigner = useCallback(async () => { + if (!periodId || !signerName.trim()) return + try { + const res = await fetch( + `/api/bookkeeping/fiscal-periods/${periodId}/arsredovisning/signatures`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ role: signerRole, signer_name: signerName.trim() }), + }, + ) + const body = await res.json() + if (!res.ok) { + toast({ + title: 'Kunde inte lägga till undertecknare', + description: body?.error?.message ?? '', + variant: 'destructive', + }) + return + } + setSignatures((prev) => [...prev, body.data as SignatureRequest]) + setSignerName('') + toast({ title: 'Undertecknare tillagd', description: `${signerRole}: ${signerName}` }) + } catch (err) { + toast({ + title: 'Kunde inte lägga till undertecknare', + description: err instanceof Error ? err.message : 'Okänt fel', + variant: 'destructive', + }) + } + }, [periodId, signerName, signerRole, toast]) + + if (!periodId) { + return ( +
+ + + + Saknar periodparameter. Öppna sidan från bokslutet via{' '} + + /bookkeeping/year-end + + . + + +
+ ) + } + + if (loading) { + return ( +
+ + + + + + + +
+ ) + } + + if (error || !data) { + return ( +
+ + + + {error ?? 'Kunde inte hämta data'} + + +
+ ) + } + + // Carry the narrative edits into the PDF URL so the download reflects + // exactly what the user typed. A future enhancement will persist + // overrides server-side; URL params get us through the merge while + // keeping the "click to download" UX. + const pdfUrl = (() => { + const qs = new URLSearchParams() + if (description !== data.forvaltningsberattelse.description) qs.set('description', description) + if (importantEvents !== data.forvaltningsberattelse.important_events) { + qs.set('events', importantEvents) + } + if (resultatdisposition !== data.forvaltningsberattelse.resultatdisposition) { + qs.set('disposition', resultatdisposition) + } + const query = qs.toString() + return `/api/bookkeeping/fiscal-periods/${periodId}/arsredovisning/pdf${query ? '?' + query : ''}` + })() + + return ( +
+ + + Tillbaka till bokslut + + + } + /> + + + + Förvaltningsberättelse — narrativ +

+ Texten nedan visas i PDF:en. Förändringar är lokala till denna sida tills + vidare; en framtida version kommer att spara dem mellan sessioner. +

+
+ +
+ +