fa394e3759
Four fixes from the exit mail Anders Orback (Center Node AB) sent hours after churning. His five points were mostly one job: reconciling skattekontot against banken before årsredovisningen. Skattekonto look-alike rows. Skatteverket splits a retroactive omprövningsbeslut across every month it re-charges and sends one transaction per month, sharing date, text and amount; only ranteberakningsdatum separates them, and we stored it but rendered it nowhere. A real company posted 15 such vouchers (67 785 kr across Feb 2025-Apr 2026) unable to tell them from duplicates of the automatic hämtning. Surface the field when it carries information: its month differs from the Datum column, or another row in the same band is otherwise indistinguishable. The list-to-voucher round trip. The verifikat list collapsed to a skeleton on every refetch and sprang back, moving rows under the pointer; only the first load shows a skeleton now. Filter state is React-only, so leaving the list loses it: add a hover-revealed open-in-new-tab affordance on the voucher list and the skattekonto page, where the link had been behind a hand-rolled opacity-0 that coarse pointers never trigger. Makulerad rendering. A stornoed verifikat now reads as struck out, per data cell rather than on the row, because text-decoration propagates and a child cannot opt out. Vouchers-per-account discoverability. /reports/huvudbok?account=1930 already existed; the palette matcher requires every token and the entry never contained the word "verifikat". Add ReportDescriptor.searchTerms plus a report-library search box. Also fixes a false "Saknar underlag" compliance chip that flashed before attachment counts resolved, and a keyboard-access regression where HOVER_REVEAL_CLASS carried focus-visible only, hiding controls inside a non-focusable wrapper from keyboard users. No migration. No write paths, storno paths or posted entries touched. Follow-ups filed: #1300 #1301 #1302 #1303 #1304 #1305 #1306 #1307 #1308. Open decision: #1305 (Omförd vs Makulerad). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
47 lines
2.3 KiB
TypeScript
47 lines
2.3 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
// Concept dry-table (scenes 9/10): borderless table straight on the panel,
|
|
// uppercase hairline heads, 13px rows, hover-revealed row controls. Shared by
|
|
// the verifikat list and the transaction inbox so the two read as the same
|
|
// instrument. Keep these in sync with .claude/rules/design.md; page-specific
|
|
// tweaks belong at the call site via cn(), not here.
|
|
export const TH_CLASS =
|
|
'px-4 py-2.5 text-left text-[11px] font-medium uppercase tracking-[0.07em] text-muted-foreground border-b border-border whitespace-nowrap'
|
|
export const TD_CLASS = 'px-4 py-[11px] border-b border-border align-top'
|
|
export const VTH_CLASS =
|
|
'py-2 pr-4 text-left text-[10.5px] font-medium uppercase tracking-[0.07em] text-muted-foreground border-b border-border'
|
|
export const VTD_CLASS = 'py-[7px] pr-4 border-b border-border/60 align-top'
|
|
export const QUIET_LINK_CLASS =
|
|
'text-[12.5px] text-muted-foreground underline decoration-border underline-offset-4 transition-colors duration-150 hover:text-foreground'
|
|
|
|
// Row controls that stay out of the way until the row is hovered. Coarse
|
|
// pointers never fire hover, so without pointer-coarse: the control would be
|
|
// permanently invisible and the action unreachable on touch. Always use this
|
|
// constant instead of hand-rolling `opacity-0 group-hover:opacity-100`.
|
|
//
|
|
// focus-within is what makes this safe on a WRAPPER: focus-visible only matches
|
|
// the element itself, so a non-focusable <span> holding the buttons would stay
|
|
// transparent while a keyboard user tabbed through the controls inside it.
|
|
export const HOVER_REVEAL_CLASS =
|
|
'opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100 focus-within:opacity-100 pointer-coarse:opacity-100'
|
|
|
|
// Animated row expansion (concept vwrap/vinner): grid-rows 0fr -> 1fr on
|
|
// mount; the global reduced-motion rule collapses the transition.
|
|
export function RowFoldout({ children }: { children: React.ReactNode }) {
|
|
const [open, setOpen] = useState(false)
|
|
useEffect(() => {
|
|
const raf = requestAnimationFrame(() => setOpen(true))
|
|
return () => cancelAnimationFrame(raf)
|
|
}, [])
|
|
return (
|
|
<div
|
|
className="grid transition-[grid-template-rows] duration-300 ease-out"
|
|
style={{ gridTemplateRows: open ? '1fr' : '0fr' }}
|
|
>
|
|
<div className="min-h-0 overflow-hidden">{children}</div>
|
|
</div>
|
|
)
|
|
}
|