'use client' import { useState, useEffect, useCallback } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Badge } from '@/components/ui/badge' import { Button } from '@/components/ui/button' import { Label } from '@/components/ui/label' import { Switch } from '@/components/ui/switch' import { ChevronDown, ChevronRight, Paperclip, AlertTriangle } from 'lucide-react' import { AccountNumber } from '@/components/ui/account-number' import JournalEntryAttachments from '@/components/bookkeeping/JournalEntryAttachments' import CorrectionEntryDialog from '@/components/bookkeeping/CorrectionEntryDialog' import type { JournalEntry, JournalEntryLine } from '@/types' const NEEDS_ATTACHMENT = new Set([ 'manual', 'bank_transaction', 'supplier_invoice_registered', 'supplier_invoice_paid', 'supplier_invoice_cash_payment', 'import', ]) interface Props { periodId?: string } export default function JournalEntryList({ periodId }: Props) { const [entries, setEntries] = useState([]) const [loading, setLoading] = useState(true) const [expandedId, setExpandedId] = useState(null) const [count, setCount] = useState(0) const [page, setPage] = useState(0) const [attachmentCounts, setAttachmentCounts] = useState>({}) const [showMissingOnly, setShowMissingOnly] = useState(false) const [correctionEntry, setCorrectionEntry] = useState(null) const pageSize = 20 const fetchAttachmentCounts = useCallback(async (entryIds: string[]) => { if (entryIds.length === 0) return try { const res = await fetch( `/api/documents/counts?journal_entry_ids=${entryIds.join(',')}` ) const { data } = await res.json() setAttachmentCounts(data || {}) } catch { console.error('Failed to fetch attachment counts') } }, []) useEffect(() => { fetchEntries() }, [periodId, page]) async function fetchEntries() { setLoading(true) const params = new URLSearchParams({ limit: String(pageSize), offset: String(page * pageSize), }) if (periodId) params.set('period_id', periodId) const res = await fetch(`/api/bookkeeping/journal-entries?${params}`) const { data, count: total } = await res.json() const loadedEntries = data || [] setEntries(loadedEntries) setCount(total || 0) setLoading(false) // Fetch attachment counts for the loaded entries const ids = loadedEntries.map((e: JournalEntry) => e.id) fetchAttachmentCounts(ids) } const handleAttachmentCountChange = useCallback((entryId: string, count: number) => { setAttachmentCounts((prev) => ({ ...prev, [entryId]: count })) }, []) const toggleExpand = (id: string) => { setExpandedId(expandedId === id ? null : id) } if (loading) { return ( Laddar verifikationer... ) } if (entries.length === 0) { return ( Inga verifikationer hittades. ) } const filteredEntries = showMissingOnly ? entries.filter( (e) => NEEDS_ATTACHMENT.has(e.source_type) && !attachmentCounts[e.id] && e.status === 'posted' ) : entries return (
{/* Missing attachment filter */}
{showMissingOnly && ( {filteredEntries.length} )}
{filteredEntries.map((entry) => { const isExpanded = expandedId === entry.id const lines = (entry.lines || []) as JournalEntryLine[] return ( {isExpanded && ( {lines.length === 0 ? (

Inga kontorader hittades för denna verifikation.

) : ( {lines .sort((a, b) => a.sort_order - b.sort_order) .map((line) => ( ))}
Konto Beskrivning Debet Kredit
{line.line_description || ''} {Number(line.debit_amount) > 0 ? Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2, }) : ''} {Number(line.credit_amount) > 0 ? Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2, }) : ''}
Summa {lines .reduce((sum, l) => sum + (Number(l.debit_amount) || 0), 0) .toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {lines .reduce((sum, l) => sum + (Number(l.credit_amount) || 0), 0) .toLocaleString('sv-SE', { minimumFractionDigits: 2 })}
)} handleAttachmentCountChange(entry.id, c)} /> {entry.status === 'posted' && entry.source_type !== 'storno' && entry.source_type !== 'correction' && (
)}
)}
) })}
{/* Correction dialog */} {correctionEntry && ( { if (!open) setCorrectionEntry(null) }} onCorrected={() => { setCorrectionEntry(null); fetchEntries() }} /> )} {/* Pagination */} {count > pageSize && (
Sida {page + 1} av {Math.ceil(count / pageSize)}
)}
) }