fix(reconciliation): declutter the Avstämning header and quiet the underlag section (#1886)
The header keeps only the page scope (räkenskapsår, intervall); the Översikt/Matcha manuellt switch moves down to the body it switches, and the Bokslutsbilagor link sits under the account rail it summarizes. The underlag section is one quiet line when nothing is attached, lists files only when there are some, and comes after the numbers instead of before them. The native file input is display:none: sr-only still leaked the browser's "Ingen fil har valts". Claude-Session: https://claude.ai/code/session_01RvFveUpbdPBXdm7f5FEYoz Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
dc92fb5c0c
commit
43a569d248
@@ -558,12 +558,6 @@ export function AccountOverview({ account, rail, otherBankAccounts = [], window,
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Underlag for the balansdag in play: the signed date, else the date the next sign-off would cover. */}
|
||||
<ReconciliationUnderlag
|
||||
accountKey={account.account_key}
|
||||
throughDate={status.signoff && status.signoff.through_date >= signoffDefaultDate ? status.signoff.through_date : signoffDefaultDate}
|
||||
/>
|
||||
|
||||
{/* Bridge: how the difference is explained. */}
|
||||
{status.bridge.length > 0 && (
|
||||
<dl className="max-w-[520px] text-[13px]">
|
||||
@@ -626,6 +620,12 @@ export function AccountOverview({ account, rail, otherBankAccounts = [], window,
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Underlag for the balansdag in play: the signed date, else the date the next sign-off would cover. */}
|
||||
<ReconciliationUnderlag
|
||||
accountKey={account.account_key}
|
||||
throughDate={status.signoff && status.signoff.through_date >= signoffDefaultDate ? status.signoff.through_date : signoffDefaultDate}
|
||||
/>
|
||||
|
||||
{items.older_unmatched_count > 0 && (
|
||||
<p className="text-[12.5px] text-muted-foreground">
|
||||
{t('older_unmatched', { count: items.older_unmatched_count })}
|
||||
|
||||
@@ -62,11 +62,13 @@ interface ReconciliationRailProps {
|
||||
accounts: ReconciliationAccount[]
|
||||
selectedKey: string | null
|
||||
onSelect: (accountKey: string) => void
|
||||
/** Rendered under the list: the way to the pärm the accounts feed. */
|
||||
footer?: React.ReactNode
|
||||
}
|
||||
|
||||
const MANUAL_OPEN_STORAGE_KEY = 'Accounted:recon-rail-manual-open'
|
||||
|
||||
export function ReconciliationRail({ accounts, selectedKey, onSelect }: ReconciliationRailProps) {
|
||||
export function ReconciliationRail({ accounts, selectedKey, onSelect, footer }: ReconciliationRailProps) {
|
||||
const t = useTranslations('reconciliation')
|
||||
const fed = accounts.filter((a) => a.kind !== 'manual')
|
||||
const manual = accounts.filter((a) => a.kind === 'manual')
|
||||
@@ -207,6 +209,7 @@ export function ReconciliationRail({ accounts, selectedKey, onSelect }: Reconcil
|
||||
</Fragment>
|
||||
)}
|
||||
</ul>
|
||||
{footer}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Paperclip } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { QUIET_LINK_CLASS, HOVER_REVEAL_CLASS } from '@/components/ui/dry-table'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { cn, formatDate } from '@/lib/utils'
|
||||
@@ -111,11 +110,16 @@ export function ReconciliationUnderlag({ accountKey, throughDate, canWrite = tru
|
||||
}
|
||||
|
||||
return (
|
||||
<section aria-label={t('heading')} className="space-y-2">
|
||||
<section aria-label={t('heading')} className="space-y-1.5">
|
||||
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
|
||||
<h3 className="text-[11px] font-medium uppercase tracking-[0.07em] text-muted-foreground">
|
||||
{t('heading_dated', { date: formatDate(throughDate) })}
|
||||
</h3>
|
||||
{attachments !== null && attachments.length === 0 && (
|
||||
<span className="text-[12.5px] text-muted-foreground" title={t('empty')}>
|
||||
{t('empty_short')}
|
||||
</span>
|
||||
)}
|
||||
{canWrite && (
|
||||
<>
|
||||
<input
|
||||
@@ -123,26 +127,24 @@ export function ReconciliationUnderlag({ accountKey, throughDate, canWrite = tru
|
||||
type="file"
|
||||
accept={ACCEPT}
|
||||
multiple
|
||||
className="sr-only"
|
||||
className="hidden"
|
||||
onChange={(e) => void upload(e.target.files)}
|
||||
aria-label={t('attach')}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => inputRef.current?.click()}
|
||||
disabled={busy !== null}
|
||||
aria-busy={busy === 'upload'}
|
||||
className={cn(QUIET_LINK_CLASS, 'inline-flex items-center gap-1')}
|
||||
>
|
||||
<Paperclip className="h-3.5 w-3.5" />
|
||||
<Paperclip className="h-3 w-3" aria-hidden="true" />
|
||||
{t('attach')}
|
||||
</Button>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{attachments === null ? null : attachments.length === 0 ? (
|
||||
<p className="text-[12.5px] text-muted-foreground">{t('empty')}</p>
|
||||
) : (
|
||||
{attachments === null || attachments.length === 0 ? null : (
|
||||
<ul className="max-w-[560px] divide-y divide-border/60 text-[13px]">
|
||||
{attachments.map((a) => (
|
||||
<li key={a.id} className="group flex items-center gap-3 py-1.5">
|
||||
|
||||
@@ -124,18 +124,6 @@ export function ReconciliationWorkspace({ initialPeriods, initialCompanyId }: Re
|
||||
}
|
||||
action={
|
||||
<div className="flex flex-wrap items-center justify-end gap-2">
|
||||
<Link href="/reports/bokslutsbilagor" className={cn(QUIET_LINK_CLASS, 'mr-2')}>
|
||||
{tParm('open_parm')}
|
||||
</Link>
|
||||
<SegmentedControl
|
||||
value={mode}
|
||||
onChange={setMode}
|
||||
aria-label={t('title')}
|
||||
options={[
|
||||
{ value: 'overview', label: t('mode_overview') },
|
||||
{ value: 'match', label: t('mode_match') },
|
||||
]}
|
||||
/>
|
||||
<FyPicker
|
||||
value={periodId}
|
||||
onChange={(id, period) => {
|
||||
@@ -205,12 +193,28 @@ export function ReconciliationWorkspace({ initialPeriods, initialCompanyId }: Re
|
||||
)
|
||||
}
|
||||
|
||||
const railFooter = (
|
||||
<Link href="/reports/bokslutsbilagor" className={cn(QUIET_LINK_CLASS, 'block px-3 pt-4 text-[12.5px]')}>
|
||||
{tParm('open_parm')}
|
||||
</Link>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{header}
|
||||
{/* How the selected account is worked: a view of the body, so it sits with the body, not with the page scope. */}
|
||||
<SegmentedControl
|
||||
value={mode}
|
||||
onChange={setMode}
|
||||
aria-label={t('title')}
|
||||
options={[
|
||||
{ value: 'overview', label: t('mode_overview') },
|
||||
{ value: 'match', label: t('mode_match') },
|
||||
]}
|
||||
/>
|
||||
{selected && mode === 'match' && (
|
||||
<div className="grid gap-8 lg:grid-cols-[220px_1fr]">
|
||||
<ReconciliationRail accounts={accounts} selectedKey={selected.account_key} onSelect={select} />
|
||||
<ReconciliationRail accounts={accounts} selectedKey={selected.account_key} onSelect={select} footer={railFooter} />
|
||||
<div className="min-w-0">
|
||||
<ManualMatchMode key={selected.account_key} account={selected} window={window} onChanged={() => void load()} />
|
||||
</div>
|
||||
@@ -220,7 +224,7 @@ export function ReconciliationWorkspace({ initialPeriods, initialCompanyId }: Re
|
||||
<AccountOverview
|
||||
key={selected.account_key}
|
||||
account={selected}
|
||||
rail={<ReconciliationRail accounts={accounts} selectedKey={selected.account_key} onSelect={select} />}
|
||||
rail={<ReconciliationRail accounts={accounts} selectedKey={selected.account_key} onSelect={select} footer={railFooter} />}
|
||||
otherBankAccounts={accounts.filter((a) => a.kind === 'bank' && a.account_key !== selected.account_key && !a.superseded_by)}
|
||||
window={window}
|
||||
onChanged={() => void load()}
|
||||
|
||||
@@ -8066,6 +8066,7 @@
|
||||
"heading_dated": "Supporting documents as of {date}",
|
||||
"attach": "Attach document",
|
||||
"empty": "No supporting documents for this date. Attach the bank statement, engagement letter or other specification the account was reconciled against.",
|
||||
"empty_short": "nothing attached",
|
||||
"remove": "Remove",
|
||||
"attached": "{name} attached",
|
||||
"removed": "{name} removed. The file stays in the archive with a note about the removal.",
|
||||
|
||||
@@ -8066,6 +8066,7 @@
|
||||
"heading_dated": "Underlag per {date}",
|
||||
"attach": "Bifoga underlag",
|
||||
"empty": "Inget underlag bifogat för det här datumet. Bifoga kontoutdrag, engagemangsbesked eller annan specifikation som kontot stämts av mot.",
|
||||
"empty_short": "inget bifogat",
|
||||
"remove": "Ta bort",
|
||||
"attached": "{name} bifogad",
|
||||
"removed": "{name} borttagen. Filen finns kvar i arkivet med en notering om borttagningen.",
|
||||
|
||||
Reference in New Issue
Block a user