Files
accounted/components/deadlines/DeadlineGroupCard.tsx
T
Jakob WennbergandClaude Fable 5 2bec5acedb feat(ui): rest-of-nav 1 — Viktiga datum, Skattekonto, Periodiseringar, Import to the concept language (#1140)
* feat(ui): concept scenes 17/24/32/33 for Viktiga datum, Skattekonto, Periodiseringar and Import (rest-of-nav 1)

Viktiga datum: thread rows with type-icon circles behind a type seg
(Alla/Skatt/Fakturering/Egna), Narmast countdown pane, Ny deadline lifted
to the page header, both banners replaced by one AttnLine, mark-done via
ConfirmDialog. DeadlineCard/DeadlineFilters die; DeadlineRow is the row.

Skattekonto: card-less saldo hero with OCR + quiet copy, shortfall AttnLine
computed from the next drain date with a betalningsuppgifter dialog
(bankgiro 5050-1055 + OCR), one dry-table with Kommande/Forfallna/
Genomforda band rows, chips only on unbooked genomforda rows, quiet
hover actions. Tabs and per-row badge noise are gone; the concept's
Saldo column is dropped because SKV stores no per-row running balance.

Periodiseringar: banner becomes an AttnLine with inline Bokfor forfallna
(now confirm-first), house seg with Aktiva count, dry-table with muted
normal states and animated RowFoldout for installments, Los upp nu as a
quiet hover link through the shared ConfirmDialog.

Import: tabs collapse into one two-column row list (Importera | Exportera)
in the concept row language; SIE export moves into a small dialog and
Molnsynkronisering folds the CloudBackupCard open in place. The
/import?view=export#sie-export and /import#cloud-backup deep links keep
working. Sandbox notice is an AttnLine.

All four pages get stagger-enter, a help popover behind ? and sv+en keys
for every new string. 9189 tests green, lint clean, guards pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(ui): align dashboard loading skeletons with the migrated page silhouettes

Folds in the parallel WIP from this checkout at the founder's request:
every loading.tsx under (dashboard) now mirrors its migrated page
row-for-row (24px title block, pill actions, borderless table heads,
single-line rows), and the shared (dashboard)/loading.tsx takes Hem's
greeting + Att gora silhouette. Also lands the pending DECISIONS.md
lines (onboarding swap plan note + rest-of-nav 1 deviations).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* polish(ui): authority logos, stat-tile skattekonto hero, quieter type on rest-of-nav 1

Viktiga datum: statutory deadlines wear the receiving authority's mark
(Skatteverket for tax dates, Bolagsverket for arsredovisning/arsstamma)
as a small badge on white; other deadlines keep the neutral type icons.
Row dates go muted, titles drop font-medium, the Narmast countdown
steps down to the house text-4xl display scale.

Skattekonto: the 32px serif hero becomes two compact metric tiles in
the KPIHeroCards idiom (saldo with the Skatteverket mark + OCR meta,
nasta dragning with date and event count), matching how numbers read
on the migrated pages.

Periodiseringar: chevron column dropped; rows expand on click exactly
like the verifikat list.

Import: provider logo chips return on Hamta fran annat system (live-
version parity) and Koppla bank carries the Enable Banking mark.

Adds skatteverket(_color), bolagsverket, enable-banking plus claude/
anthropic marks (for future use) under public/logos/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* polish(ui): keep Importera and Exportera as separate tabs on the import page

Founder feedback: the merged two-column landing goes back to the
familiar split. The house seg switches between the Importera rows and
the Exportera rows (SIE 4 dialog + Molnsynkronisering fold), ?view=export
selects the export tab again and the hash deep links flip to it before
opening their surface. Row language and logo chips stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): readable Enable Banking mark and full-width skattekonto

The enable-banking.webp is the full stacked logo in white-on-transparent:
invisible on the light chip and mush at 16px. The chip now uses a cropped
368px icon square (enable-banking-icon.png) with the marketing site's
grayscale+brightness treatment in light mode and a white lift in dark.

Skattekonto loses its max-w-3xl cap so the table stretches the content
column exactly like Bokforing and Transaktioner; the saldo tiles take
KPI-card width (lg:grid-cols-4). Import's tab columns stretch too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(ui): address review-bot findings on rest-of-nav 1

CodeRabbit triage, all three confirmed real: the Bokfor forfallna attn
action is hidden for read-only users instead of rendering a no-op link;
a failed deadline edit rethrows so the form stays open with the user's
input; authority marks are reserved for statutory (system-generated)
deadlines: a manual tax-category deadline keeps the neutral icon.

Compliance swarm's two high findings verified clean, no change needed:
/api/bookkeeping/accruals/:id/dissolve and /api/deadlines/:id (+ /complete)
all run through withRouteContext with company_id scoping and 404 on miss.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 12:36:08 +02:00

115 lines
3.9 KiB
TypeScript

'use client'
import { useTranslations } from 'next-intl'
import { Deadline } from '@/types'
import { cn } from '@/lib/utils'
import { isDeadlineOverdue } from '@/lib/calendar/utils'
import { QUIET_LINK_CLASS } from '@/components/ui/dry-table'
import { deadlineDateLabel } from './DeadlineRow'
import { Pencil } from 'lucide-react'
/**
* System tax deadlines that legally share the skattekonto date ("den 12:e"):
* for a small monthly-moms employer, moms + AGI (+ debiterad preliminärskatt)
* all fall due the same day. Rendering them as one grouped row instead of
* 2-3 identical-date rows keeps the list scannable.
*/
export const SKATTEKONTO_GROUP_TYPES = new Set([
'moms_monthly',
'moms_quarterly',
'moms_yearly',
'f_skatt',
'arbetsgivardeklaration',
'skatteinbetalning',
])
export function isSkattekontoDeadline(d: Deadline): boolean {
return (
!d.is_completed &&
d.source === 'system' &&
d.tax_deadline_type !== null &&
SKATTEKONTO_GROUP_TYPES.has(d.tax_deadline_type)
)
}
interface DeadlineGroupRowProps {
/** Two or more skattekonto deadlines sharing the same due_date. */
deadlines: Deadline[]
onEdit?: (deadline: Deadline) => void
/** "Markera klar": the list confirms before toggling. */
onRequestToggle: (deadline: Deadline) => void
}
/**
* One thread row for all skattekonto obligations on a shared due date
* (concept scene 17 row language): the date renders once, each obligation is
* a sub-row with its own quiet "Markera klar".
*/
export function DeadlineGroupCard({ deadlines, onEdit, onRequestToggle }: DeadlineGroupRowProps) {
const t = useTranslations('deadlines')
const first = deadlines[0]
const overdue = deadlines.some(isDeadlineOverdue)
return (
<div className="flex items-start gap-3 py-3 -mx-2 px-2">
{/* Shared skattekonto date: always Skatteverket's mark (white ground,
badge not chrome). */}
<span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full border border-border bg-white">
<img src="/logos/skatteverket_color.svg" alt="Skatteverket" className="h-4 w-4 object-contain" />
</span>
<div className="min-w-0 flex-1">
<p className="text-sm leading-6">
<span className="tabular-nums text-muted-foreground">
{deadlineDateLabel(first.due_date)}
</span>
<span className="text-muted-foreground/50"> · </span>
<span>{t('group_skattekonto_title')}</span>
</p>
<p
className={cn(
'text-xs leading-5',
overdue ? 'text-destructive' : 'text-muted-foreground',
)}
>
{t('group_same_day', { count: deadlines.length })}
</p>
<div className="mt-1">
{deadlines.map((deadline) => (
<div
key={deadline.id}
onClick={() => onEdit?.(deadline)}
className={cn(
'group flex items-center gap-3 rounded-md py-1.5 -mx-2 px-2 transition-colors duration-150',
onEdit && 'cursor-pointer hover:bg-secondary/35',
)}
>
<p className="min-w-0 flex-1 truncate text-sm">{deadline.title}</p>
<button
type="button"
onClick={(e) => {
e.stopPropagation()
onRequestToggle(deadline)
}}
className={cn(
QUIET_LINK_CLASS,
'shrink-0 opacity-0 transition-opacity group-hover:opacity-100 focus-visible:opacity-100',
)}
>
{t('group_mark_done')}
</button>
{onEdit && (
<Pencil
className="h-3.5 w-3.5 shrink-0 text-muted-foreground/0 transition-colors group-hover:text-muted-foreground/50"
aria-hidden="true"
/>
)}
</div>
))}
</div>
</div>
</div>
)
}