feat: UI slop cleanup, invoice icon/header polish + year-end in Rapporter, journal-list DataList refactor (#847)
UI cleanup: removed AI-slop (redundant suppliers subtitle, decorative Sparkles glyph), decluttered the article-detail header (single status badge + muted type · #number), standardized the invoice icon Receipt→ReceiptText (no $ in a SEK app), and matched ReportExportMenu trigger size to the primary CTA on list pages. Bookkeeping: surfaced year-end closing in Rapporter (catalog descriptor) and dropped the redundant header button; refactored JournalEntryList to DataList primitives + chunked /api/documents/counts in 50-ID batches (large pages previously 400'd); added optional fraction-digit overrides to formatCurrency. The fiscal-year lock indicator is preserved as a labeled Låst/Stängt badge in FiscalYearSelector. All PR-bot findings triaged as false positives (unused import, formatCurrency öre, lock indicator) or intentional design (year-end placement, empty-state messaging). CI green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
db843a7a5b
commit
d63d2aecf0
@@ -191,15 +191,13 @@ export default function ArticleDetailPage({
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{article.name}</h1>
|
||||
<div className="flex items-center gap-2 mt-1">
|
||||
<Badge variant="secondary">{t(ARTICLE_TYPE_KEY[article.type])}</Badge>
|
||||
{article.article_number && (
|
||||
<span className="text-sm text-muted-foreground tabular-nums">
|
||||
{article.article_number}
|
||||
</span>
|
||||
)}
|
||||
<Badge variant={article.active ? 'success' : 'secondary'}>
|
||||
{article.active ? t('status_active') : t('status_inactive')}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground tabular-nums">
|
||||
{t(ARTICLE_TYPE_KEY[article.type])}
|
||||
{article.article_number ? ` · #${article.article_number}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -240,6 +240,7 @@ function ArticlesPageInner() {
|
||||
action={
|
||||
<div className="flex items-center gap-2">
|
||||
<ReportExportMenu
|
||||
size="default"
|
||||
items={[
|
||||
{ format: 'xlsx', href: '/api/export/articles' },
|
||||
{ format: 'csv', href: '/api/export/articles?format=csv' },
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
|
||||
@@ -11,7 +10,7 @@ import { type FormLine } from '@/components/bookkeeping/JournalEntryForm'
|
||||
import NewJournalEntryDialog, { type CopyPrefill } from '@/components/bookkeeping/NewJournalEntryDialog'
|
||||
import ChartOfAccountsManager from '@/components/bookkeeping/ChartOfAccountsManager'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { Lock, Plus } from 'lucide-react'
|
||||
import { Plus } from 'lucide-react'
|
||||
import { PageHeader } from '@/components/ui/page-header'
|
||||
import { formatVoucher } from '@/lib/bookkeeping/voucher-series-resolver'
|
||||
import type { JournalEntry, JournalEntryLine } from '@/types'
|
||||
@@ -129,29 +128,21 @@ export default function BookkeepingPage() {
|
||||
<PageHeader
|
||||
title={t('title')}
|
||||
action={
|
||||
<div className="flex gap-2 w-full sm:w-auto">
|
||||
<Button
|
||||
className="w-full sm:w-auto"
|
||||
onClick={() => {
|
||||
setCopyPrefill(null)
|
||||
setShowNewEntry(true)
|
||||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t('tab_new_entry')}
|
||||
{nextVoucher && (
|
||||
<span className="ml-1 text-primary-foreground/70 tabular-nums">
|
||||
({nextVoucher.series}{nextVoucher.next})
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
<Button variant="outline" asChild className="w-full sm:w-auto">
|
||||
<Link href="/bookkeeping/year-end">
|
||||
<Lock className="mr-2 h-4 w-4" />
|
||||
{t('year_end')}
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full sm:w-auto"
|
||||
onClick={() => {
|
||||
setCopyPrefill(null)
|
||||
setShowNewEntry(true)
|
||||
}}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
{t('tab_new_entry')}
|
||||
{nextVoucher && (
|
||||
<span className="ml-1 text-primary-foreground/70 tabular-nums">
|
||||
({nextVoucher.series}{nextVoucher.next})
|
||||
</span>
|
||||
)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
Edit2,
|
||||
Trash2,
|
||||
Loader2,
|
||||
Receipt,
|
||||
ReceiptText,
|
||||
Lock,
|
||||
} from 'lucide-react'
|
||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
||||
@@ -313,7 +313,7 @@ export default function CustomerDetailPage({
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Receipt className="h-4 w-4 text-muted-foreground" />
|
||||
<ReceiptText className="h-4 w-4 text-muted-foreground" />
|
||||
<span>{t('invoice_count', { count: customer.invoices?.length || 0 })}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -336,7 +336,7 @@ export default function CustomerDetailPage({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base flex items-center gap-2">
|
||||
<Receipt className="h-4 w-4" />
|
||||
<ReceiptText className="h-4 w-4" />
|
||||
{t('section_invoices')}
|
||||
{customer.invoices?.length > 0 && (
|
||||
<Badge variant="secondary">{customer.invoices.length}</Badge>
|
||||
|
||||
@@ -244,6 +244,7 @@ function CustomersPageInner() {
|
||||
action={
|
||||
<div className="flex items-center gap-2">
|
||||
<ReportExportMenu
|
||||
size="default"
|
||||
items={[
|
||||
{ format: 'xlsx', href: '/api/export/customers' },
|
||||
{ format: 'csv', href: '/api/export/customers?format=csv' },
|
||||
|
||||
@@ -24,7 +24,7 @@ import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { invoiceDisplayNumber } from '@/lib/invoices/display'
|
||||
import { getDisplayTotal } from '@/lib/invoices/rounding'
|
||||
import { Plus, Search, Receipt, Lock, Repeat } from 'lucide-react'
|
||||
import { Plus, Search, ReceiptText, Lock, Repeat } from 'lucide-react'
|
||||
import { EmptyInvoices } from '@/components/ui/empty-state'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
import { useCanWrite } from '@/lib/hooks/use-can-write'
|
||||
@@ -256,7 +256,7 @@ export default function InvoicesPage() {
|
||||
) : filteredInvoices.length === 0 ? (
|
||||
searchTerm ? (
|
||||
<DataListEmpty
|
||||
icon={<Receipt className="h-6 w-6" />}
|
||||
icon={<ReceiptText className="h-6 w-6" />}
|
||||
title={t('no_search_results_title')}
|
||||
description={t('no_search_results_description', { term: searchTerm })}
|
||||
/>
|
||||
@@ -264,7 +264,7 @@ export default function InvoicesPage() {
|
||||
<EmptyInvoices />
|
||||
) : (
|
||||
<DataListEmpty
|
||||
icon={<Receipt className="h-6 w-6" />}
|
||||
icon={<ReceiptText className="h-6 w-6" />}
|
||||
title={t('no_category_title')}
|
||||
description={t('no_category_description')}
|
||||
/>
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
ClipboardCheck,
|
||||
ArrowLeftRight,
|
||||
Users,
|
||||
Receipt,
|
||||
ReceiptText,
|
||||
Bot,
|
||||
BookOpen,
|
||||
ChevronDown,
|
||||
@@ -63,14 +63,14 @@ import { MatchTransactionInvoicePreview } from '@/components/bookkeeping/MatchTr
|
||||
const OPERATION_LABEL_KEYS: Record<string, { labelKey: string; icon: typeof ArrowLeftRight; variant: 'default' | 'secondary' | 'outline' }> = {
|
||||
categorize_transaction: { labelKey: 'type_categorize_transaction', icon: ArrowLeftRight, variant: 'default' },
|
||||
create_customer: { labelKey: 'type_create_customer', icon: Users, variant: 'secondary' },
|
||||
create_invoice: { labelKey: 'type_create_invoice', icon: Receipt, variant: 'outline' },
|
||||
create_invoice: { labelKey: 'type_create_invoice', icon: ReceiptText, variant: 'outline' },
|
||||
create_transaction: { labelKey: 'type_create_transaction', icon: ArrowLeftRight, variant: 'secondary' },
|
||||
create_voucher: { labelKey: 'type_create_voucher', icon: BookOpen, variant: 'outline' },
|
||||
correct_entry: { labelKey: 'type_correct_entry', icon: BookOpen, variant: 'outline' },
|
||||
reverse_entry: { labelKey: 'type_reverse_entry', icon: BookOpen, variant: 'outline' },
|
||||
mark_invoice_paid: { labelKey: 'type_mark_invoice_paid', icon: Receipt, variant: 'default' },
|
||||
send_invoice: { labelKey: 'type_send_invoice', icon: Receipt, variant: 'outline' },
|
||||
mark_invoice_sent: { labelKey: 'type_mark_invoice_sent', icon: Receipt, variant: 'outline' },
|
||||
mark_invoice_paid: { labelKey: 'type_mark_invoice_paid', icon: ReceiptText, variant: 'default' },
|
||||
send_invoice: { labelKey: 'type_send_invoice', icon: ReceiptText, variant: 'outline' },
|
||||
mark_invoice_sent: { labelKey: 'type_mark_invoice_sent', icon: ReceiptText, variant: 'outline' },
|
||||
match_transaction_invoice: { labelKey: 'type_match_transaction_invoice', icon: ArrowLeftRight, variant: 'secondary' },
|
||||
}
|
||||
|
||||
|
||||
@@ -116,12 +116,10 @@ export default function SuppliersPage() {
|
||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl md:text-3xl tracking-tight">{t('title')}</h1>
|
||||
<p className="text-muted-foreground">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<ReportExportMenu
|
||||
size="default"
|
||||
items={[
|
||||
{ format: 'xlsx', href: '/api/export/suppliers' },
|
||||
{ format: 'csv', href: '/api/export/suppliers?format=csv' },
|
||||
|
||||
Reference in New Issue
Block a user