diff --git a/CLAUDE.md b/CLAUDE.md index ed5a07d2..99ff8b00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -408,6 +408,7 @@ export async function POST(request: Request) { 5. **Never modify existing migrations** — create new ones 6. **Never modify enforcement triggers** (migration 017) — legally required 7. **Apply via Supabase MCP tool**: `mcp__plugin_supabase_supabase__apply_migration` +8. **Always include `NOTIFY pgrst, 'reload schema'`** at the end of migrations that alter table structure (ADD/DROP COLUMN, CREATE TABLE, ALTER TYPE). Without this, PostgREST serves stale schema until next reload. --- diff --git a/app/(dashboard)/bookkeeping/[id]/page.tsx b/app/(dashboard)/bookkeeping/[id]/page.tsx index 5b51cb46..e9f81979 100644 --- a/app/(dashboard)/bookkeeping/[id]/page.tsx +++ b/app/(dashboard)/bookkeeping/[id]/page.tsx @@ -1,27 +1,39 @@ 'use client' import { useState, useEffect, useCallback, use } from 'react' +import { useRouter } from 'next/navigation' import Link from 'next/link' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { AccountNumber } from '@/components/ui/account-number' -import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock } from 'lucide-react' +import { Textarea } from '@/components/ui/textarea' +import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X } from 'lucide-react' import { useCanWrite } from '@/lib/hooks/use-can-write' import JournalEntryAttachments from '@/components/bookkeeping/JournalEntryAttachments' import JournalEntryStatusBadge, { sourceTypeLabels } from '@/components/bookkeeping/JournalEntryStatusBadge' import CorrectionEntryDialog from '@/components/bookkeeping/CorrectionEntryDialog' import CorrectionChain from '@/components/bookkeeping/CorrectionChain' +import { ConfirmationDialog } from '@/components/ui/confirmation-dialog' +import { useToast } from '@/components/ui/use-toast' import type { JournalEntry, JournalEntryLine } from '@/types' export default function JournalEntryDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params) + const router = useRouter() const { canWrite } = useCanWrite() + const { toast } = useToast() const [entry, setEntry] = useState(null) const [chain, setChain] = useState([]) const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState(null) const [showCorrection, setShowCorrection] = useState(false) + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) + const [isDeleting, setIsDeleting] = useState(false) + const [isLastInSeries, setIsLastInSeries] = useState(false) const [attachmentCount, setAttachmentCount] = useState(0) + const [editingNotes, setEditingNotes] = useState(false) + const [notesValue, setNotesValue] = useState('') + const [savingNotes, setSavingNotes] = useState(false) const fetchData = useCallback(async () => { setIsLoading(true) @@ -36,6 +48,7 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i const { data } = await res.json() setEntry(data.entry) setChain(data.chain) + setIsLastInSeries(data.is_last_in_series ?? false) } catch { setError('Kunde inte hämta verifikation') } finally { @@ -43,6 +56,50 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i } }, [id]) + const saveNotes = useCallback(async (value: string) => { + setSavingNotes(true) + try { + const res = await fetch(`/api/bookkeeping/journal-entries/${id}/notes`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ notes: value || null }), + }) + if (res.ok) { + setEntry(prev => prev ? { ...prev, notes: value || null } : prev) + setEditingNotes(false) + } else { + toast({ title: 'Kunde inte spara anteckning', variant: 'destructive' }) + } + } catch { + toast({ title: 'Kunde inte spara anteckning', variant: 'destructive' }) + } finally { + setSavingNotes(false) + } + }, [id, toast]) + + const handleDelete = useCallback(async () => { + setIsDeleting(true) + try { + const res = await fetch(`/api/bookkeeping/journal-entries/${id}`, { method: 'DELETE' }) + const result = await res.json() + if (res.ok) { + toast({ + title: 'Verifikat raderat', + description: `Verifikat ${result.data?.voucher_series ?? ''}${result.data?.voucher_number ?? ''} har raderats.`, + }) + router.push('/bookkeeping') + } else { + toast({ title: 'Kunde inte radera', description: result.error, variant: 'destructive' }) + setShowDeleteConfirm(false) + } + } catch { + toast({ title: 'Kunde inte radera verifikat', variant: 'destructive' }) + setShowDeleteConfirm(false) + } finally { + setIsDeleting(false) + } + }, [id, router, toast]) + useEffect(() => { fetchData() }, [fetchData]) @@ -120,18 +177,35 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i

{entry.description}

- {canCorrect && ( - + {entry.status === 'posted' && ( +
+ {isLastInSeries && ( + + )} + {canCorrect && ( + + )} +
)} @@ -156,6 +230,61 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i Typ {sourceTypeLabels[entry.source_type] || entry.source_type} + {/* Notes — always editable (internal metadata, not BFL verifikation content) */} +
+
+ + + Anteckning + + {!editingNotes && canWrite && ( + + )} +
+ {editingNotes ? ( +
+