Files
accounted/components/salary/run/RunCalculationDetails.tsx
T
a92c492dbe refactor(ui): record detail pages as documents, not card piles (#1739)
Bring every record detail page onto the register-detail document grammar
from #1624 (DetailSection/DefRow, one status element per the list pages'
chips-mark-exceptions rule, one primary next step plus Förhandsgranska
visible and everything else behind a ⋯ overflow menu, line tables on the
dry-table idiom with the headline total in the serif):

- invoices/[id] (11 cards, 13-button toolbar): Kund | Detaljer rows,
  Fakturarader table + totals, Anteckningar, Betalning, Påminnelser,
  Utskickshistorik (InvoiceDeliveryHistory flattened); title carries the
  doc type, related documents become link rows
- supplier-invoices/[id], bookkeeping/[id] (serif title instead of
  font-mono, JournalEntryAttachments variant="section", CorrectionChain
  flattened), invoices/[id]/credit, assets/[id]/dispose (form as Fönster
  rows), salary employees/[id] (edit form behind Redigera in a dialog,
  Ingående saldon collapsed), salary runs/[id] + run panels (Betalfil,
  Skattebetalning, AGI, förmåner, override) and the payslip page
- DetailSection gains an optional help slot (convention 7)

Styling/structure only: no API, fetch, validation, state, dialog or
permission change; every action stays reachable.

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 12:29:43 +02:00

59 lines
2.1 KiB
TypeScript

'use client'
import { useTranslations } from 'next-intl'
import { DetailSection } from '@/components/ui/detail-section'
import { TaxTableStatus } from '@/components/salary/TaxTableStatus'
import { formatCurrency } from '@/lib/utils'
import type { SalaryRunEmployee } from '@/types'
type SreWithEmployee = SalaryRunEmployee & {
employee?: { first_name: string; last_name: string }
}
interface RunCalculationDetailsProps {
periodYear: number
employees: SalaryRunEmployee[]
}
export function RunCalculationDetails({ periodYear, employees }: RunCalculationDetailsProps) {
const t = useTranslations('salary_run')
const withBreakdown = employees.filter(e => e.calculation_breakdown)
if (withBreakdown.length === 0) return null
return (
<DetailSection kicker={t('calculation_details_title')}>
<div className="space-y-6">
<TaxTableStatus year={periodYear} compact />
{withBreakdown.map(sre => {
const breakdown = sre.calculation_breakdown as {
steps?: Array<{ label: string; formula: string; output: number | null }>
}
const employee = (sre as SreWithEmployee).employee
return (
<div key={sre.id}>
<h4 className="text-sm font-medium">
{employee
? `${employee.first_name} ${employee.last_name}`
: sre.employee_id.slice(0, 8)}
</h4>
{/* Flat calculation steps: one hairline per step, no box. */}
<div className="mt-1 divide-y divide-border text-xs">
{(breakdown?.steps || []).map((step, i) => (
<div key={i} className="flex justify-between gap-4 py-1">
<span className="min-w-0 text-muted-foreground">
{step.label}: <span className="font-mono">{step.formula}</span>
</span>
{step.output !== null && (
<span className="shrink-0 tabular-nums">{formatCurrency(step.output)}</span>
)}
</div>
))}
</div>
</div>
)
})}
</div>
</DetailSection>
)
}