diff --git a/app/api/bookkeeping/journal-entries/route.ts b/app/api/bookkeeping/journal-entries/route.ts index 8a518f82..9bdaae1b 100644 --- a/app/api/bookkeeping/journal-entries/route.ts +++ b/app/api/bookkeeping/journal-entries/route.ts @@ -22,14 +22,26 @@ export async function GET(request: Request) { const offset = parseInt(searchParams.get('offset') || '0') const dateFrom = searchParams.get('date_from') const dateTo = searchParams.get('date_to') + const sortDate = searchParams.get('sort_date') // 'asc' | 'desc' + + const dateAscending = sortDate === 'asc' let query = supabase .from('journal_entries') .select('*, lines:journal_entry_lines(*)', { count: 'exact' }) .eq('user_id', user.id) - .order('voucher_series', { ascending: true }) - .order('voucher_number', { ascending: true }) - .range(offset, offset + limit - 1) + + if (sortDate === 'asc' || sortDate === 'desc') { + query = query + .order('entry_date', { ascending: dateAscending }) + .order('voucher_number', { ascending: dateAscending }) + } else { + query = query + .order('voucher_series', { ascending: true }) + .order('voucher_number', { ascending: true }) + } + + query = query.range(offset, offset + limit - 1) if (periodId) { query = query.eq('fiscal_period_id', periodId) diff --git a/components/bookkeeping/JournalEntryList.tsx b/components/bookkeeping/JournalEntryList.tsx index 22103fe9..f4f5c273 100644 --- a/components/bookkeeping/JournalEntryList.tsx +++ b/components/bookkeeping/JournalEntryList.tsx @@ -6,7 +6,8 @@ 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 { ArrowDownNarrowWide, ArrowUpNarrowWide, ChevronDown, ChevronRight, Paperclip, AlertTriangle, X } from 'lucide-react' +import { Input } from '@/components/ui/input' import { AccountNumber } from '@/components/ui/account-number' import JournalEntryAttachments from '@/components/bookkeeping/JournalEntryAttachments' import CorrectionEntryDialog from '@/components/bookkeeping/CorrectionEntryDialog' @@ -34,8 +35,15 @@ export default function JournalEntryList({ periodId }: Props) { const [attachmentCounts, setAttachmentCounts] = useState>({}) const [showMissingOnly, setShowMissingOnly] = useState(false) const [correctionEntry, setCorrectionEntry] = useState(null) + const [dateSortDir, setDateSortDir] = useState<'desc' | 'asc'>('desc') + const [dateFrom, setDateFrom] = useState('') + const [dateTo, setDateTo] = useState('') + const [dateFromInput, setDateFromInput] = useState('') + const [dateToInput, setDateToInput] = useState('') const pageSize = 20 + const isValidDate = (v: string) => /^\d{4}-\d{2}-\d{2}$/.test(v) && !isNaN(Date.parse(v)) + const fetchAttachmentCounts = useCallback(async (entryIds: string[]) => { if (entryIds.length === 0) return try { @@ -54,8 +62,11 @@ export default function JournalEntryList({ periodId }: Props) { const params = new URLSearchParams({ limit: String(pageSize), offset: String(page * pageSize), + sort_date: dateSortDir, }) if (periodId) params.set('period_id', periodId) + if (dateFrom) params.set('date_from', dateFrom) + if (dateTo) params.set('date_to', dateTo) const res = await fetch(`/api/bookkeeping/journal-entries?${params}`) const { data, count: total } = await res.json() @@ -71,7 +82,7 @@ export default function JournalEntryList({ periodId }: Props) { useEffect(() => { fetchEntries() - }, [periodId, page]) + }, [periodId, page, dateSortDir, dateFrom, dateTo]) const handleAttachmentCountChange = useCallback((entryId: string, count: number) => { setAttachmentCounts((prev) => ({ ...prev, [entryId]: count })) @@ -112,21 +123,80 @@ export default function JournalEntryList({ periodId }: Props) { return (
- {/* Missing attachment filter */} -
- - - {showMissingOnly && ( - - {filteredEntries.length} - - )} + {/* Filters and sorting */} +
+
+ + + {showMissingOnly && ( + + {filteredEntries.length} + + )} +
+ +
+ setDateFromInput(e.target.value)} + onBlur={() => { + const v = dateFromInput.trim() + const next = v === '' ? '' : isValidDate(v) ? v : dateFrom + setDateFromInput(next) + if (next !== dateFrom) { setDateFrom(next); setPage(0) } + }} + onKeyDown={(e) => { + if (e.key === 'Enter') (e.target as HTMLInputElement).blur() + }} + className="h-8 w-[145px] text-xs" + /> + setDateToInput(e.target.value)} + onBlur={() => { + const v = dateToInput.trim() + const next = v === '' ? '' : isValidDate(v) ? v : dateTo + setDateToInput(next) + if (next !== dateTo) { setDateTo(next); setPage(0) } + }} + onKeyDown={(e) => { + if (e.key === 'Enter') (e.target as HTMLInputElement).blur() + }} + className="h-8 w-[145px] text-xs" + /> + {(dateFrom || dateTo) && ( + + )} +