Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
import { Typography, Tooltip } from 'antd';
|
|
|
|
|
import { ArrowUpOutlined, ArrowDownOutlined, MinusOutlined } from '@ant-design/icons';
|
|
|
|
|
import { formatCurrency } from '@/lib/formatters';
|
|
|
|
|
import { accountingColors } from '@/styles/theme';
|
|
|
|
|
import { typography, getAmountColor } from '@/styles/designTokens';
|
|
|
|
|
|
|
|
|
|
const { Text } = Typography;
|
|
|
|
|
|
|
|
|
|
export type AmountType = 'debit' | 'credit' | 'neutral' | 'auto';
|
|
|
|
|
|
|
|
|
|
export interface AmountTextProps {
|
|
|
|
|
/** The amount to display */
|
|
|
|
|
amount: number;
|
|
|
|
|
/** Override automatic color detection */
|
|
|
|
|
type?: AmountType;
|
|
|
|
|
/** Whether to show the sign (+/-) */
|
|
|
|
|
showSign?: boolean;
|
|
|
|
|
/** Whether to show currency suffix */
|
|
|
|
|
showCurrency?: boolean;
|
|
|
|
|
/** Currency suffix text */
|
|
|
|
|
currencySuffix?: string;
|
|
|
|
|
/** Text size variant */
|
|
|
|
|
size?: 'small' | 'default' | 'large';
|
|
|
|
|
/** Whether to use tabular (monospace) numbers */
|
|
|
|
|
tabular?: boolean;
|
|
|
|
|
/** Whether to show tooltip with full precision */
|
|
|
|
|
showTooltip?: boolean;
|
|
|
|
|
/** Show icon indicator for accessibility (not color-only) */
|
|
|
|
|
showIcon?: boolean;
|
|
|
|
|
/** Additional CSS class */
|
|
|
|
|
className?: string;
|
|
|
|
|
/** Inline style overrides */
|
|
|
|
|
style?: React.CSSProperties;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A consistent component for displaying monetary amounts.
|
|
|
|
|
* Automatically colors based on value (positive = credit/green, negative = debit/red).
|
|
|
|
|
*
|
|
|
|
|
* @example
|
|
|
|
|
* <AmountText amount={1234.56} />
|
|
|
|
|
* <AmountText amount={-500} type="debit" showSign />
|
|
|
|
|
* <AmountText amount={0} type="neutral" />
|
|
|
|
|
*/
|
|
|
|
|
export function AmountText({
|
|
|
|
|
amount,
|
|
|
|
|
type = 'auto',
|
|
|
|
|
showSign = false,
|
|
|
|
|
showCurrency = true,
|
|
|
|
|
currencySuffix = 'kr.',
|
|
|
|
|
size = 'default',
|
|
|
|
|
tabular = true,
|
|
|
|
|
showTooltip = false,
|
|
|
|
|
showIcon = false,
|
|
|
|
|
className,
|
|
|
|
|
style,
|
|
|
|
|
}: AmountTextProps) {
|
|
|
|
|
const getColor = (): string => {
|
|
|
|
|
if (type === 'auto') {
|
|
|
|
|
return getAmountColor(amount);
|
|
|
|
|
}
|
|
|
|
|
if (type === 'neutral') {
|
|
|
|
|
return accountingColors.neutral;
|
|
|
|
|
}
|
|
|
|
|
return accountingColors[type];
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getFontSize = (): number => {
|
|
|
|
|
switch (size) {
|
|
|
|
|
case 'small':
|
|
|
|
|
return typography.caption.fontSize;
|
|
|
|
|
case 'large':
|
|
|
|
|
return typography.h3.fontSize;
|
|
|
|
|
default:
|
|
|
|
|
return typography.body.fontSize;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formatAmount = (): string => {
|
|
|
|
|
const formatted = formatCurrency(Math.abs(amount));
|
Audit v2: fix security, data integrity, compliance, bugs, encoding, UX
Backend Security & Data Integrity:
- Block negative debit/credit amounts that bypass balance validation
- Require document date at posting (was optional, bypassing fiscal year checks)
- Fix event sourcing anti-pattern: timestamps now stored in events, not UtcNow in Apply
- Add [Authorize] to BankingController OAuth callback
- Add company access check on attachment downloads
- Validate CVR in CompanyAggregate.Create and CompanyAggregate.Update
- Require company CVR for invoice creation (Momsloven §52)
- Delete leftover WeatherForecastController
- Fix duplicate migration number 007 (renamed to 007b)
- Remove dead code in VatCalculationService (identical if/else branches)
Accounting Compliance:
- Add missing VAT accounts to StandardDanishAccounts (5610, 5611, 5620)
- Populate SAF-T TaxInformation on transaction lines (was always null)
- Add AuditFileCountry and TaxRegistrationNumber to SAF-T header
Critical Frontend Bugs:
- Fix Dashboard <a href> causing full page reloads (now uses React Router Link)
- Wire Kassekladde filters to actual data (account, status, date range)
- Pre-populate form when editing existing Kassekladde drafts
- Add detail drawer for "Vis detaljer" action (was just a toast)
- Toggle advanced filters with "Flere filtre" button
- CloseFiscalYearWizard now actually posts closing entries via mutations
- "Create next year" checkbox now creates the next fiscal year
Danish Character Encoding (~50 fixes):
- Fix ø/æ/å across Momsindberetning, DocumentUploadModal, Bankafstemning,
Kontooversigt, CloseFiscalYearWizard, vatCodes, periodStore, periods,
accounting, types/periods
Dead Buttons & UX:
- Disable Momsindberetning PDF/Export buttons with tooltips
- FiscalYearSelector "Administrer" now navigates to Settings
- Settings bank tab now uses real BankConnectionsTab component
- Bankafstemning save button disabled with development tooltip
- Replace hardcoded account options with real API data (Bankafstemning, Fakturaer)
- Header help button shows info message, notification bell shows popover
Consistency & Quality:
- Remove 7 console.log statements from production code
- Adopt PageHeader on 6 remaining pages (Kreditnotaer, Settings, Admin, etc.)
- Standardize loading states to Skeleton pattern (5 pages)
- Replace deprecated bodyStyle prop on Ant Design Cards
- Standardize date format to DD-MM-YYYY
- Fix sidebar width mismatch in designTokens
- Fix Kontooversigt breadcrumb pointing to non-existent route
Accessibility:
- Add aria-label to sidebar navigation
- Add +/- prefix to AmountText for color-blind users
- Fix CompanySwitcher permanent skeleton when no companies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 00:18:19 +01:00
|
|
|
// Always show +/- prefix for non-zero amounts (accessibility: not color-only)
|
|
|
|
|
// When showSign is explicitly true, same behavior; kept for API compatibility
|
Audit v3: VAT alignment, security, encoding, UX, compliance
VAT System Alignment (LEGAL - Critical):
- Align frontend VAT codes with backend (S25→U25, K25→I25, etc.)
- Add missing codes: UEU, IVV, IVY, REP
- Fix output VAT account 5710→5611 to match StandardDanishAccounts
- Invoice posting now checks fiscal year status before allowing send
- Disallow custom invoice number override (always use auto-numbering)
Security:
- Fix open redirect in AuthController (validate returnUrl is local)
- Store seller CVR/name/address on invoice events (Momsloven §52)
Backend Compliance:
- Add description validation at posting (Bogføringsloven §7)
- SAF-T: add DefaultCurrencyCode, TaxAccountingBasis to header
- SAF-T: add TaxTable to MasterFiles with all VAT codes
- SAF-T: always write balance elements even when zero
- Add financial income account 9100 Renteindtægter
Danish Encoding (~25 fixes):
- Kassekladde: Bogført, Bogføring, Vælg, være, på, Tilføj, Differens
- AttachmentUpload: træk, Understøtter, påkrævet, Bogføringsloven
- keyboardShortcuts: Bogfør, Bogføring display name
- ShortcutsHelpModal: åbne
- DataTable: Genindlæs
- documentProcessing: være
- CloseFiscalYearWizard: årsafslutning
Bugs Fixed:
- Non-null assertion crashes in Kunder.tsx and Produkter.tsx (company!.id)
- StatusBadge typo "Succces"→"Succes"
- HTML entity ø in Kassekladde→proper UTF-8
- AmountText showSign prop was dead code (true || showSign)
UX Improvements:
- Add PageHeader to Bankafstemning and Dashboard loading/empty states
- Responsive columns in Bankafstemning (xs/sm/lg breakpoints)
- Disable misleading buttons: Settings preferences, Kontooversigt edit,
Loenforstaelse export — with tooltips explaining status
- Add DemoDataDisclaimer to UserSettings
- Fix breadcrumb self-references on 3 pages
- Replace Dashboard fake progress bar with honest message
- Standardize date format DD-MM-YYYY in Bankafstemning and Ordrer
- Replace Input type="number" with InputNumber in Ordrer
Quality:
- Remove 8 redundant console.error statements
- Fix Kreditnotaer breadcrumb "Salg"→"Fakturering" for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 01:15:45 +01:00
|
|
|
const alwaysSign = showSign;
|
Audit v2: fix security, data integrity, compliance, bugs, encoding, UX
Backend Security & Data Integrity:
- Block negative debit/credit amounts that bypass balance validation
- Require document date at posting (was optional, bypassing fiscal year checks)
- Fix event sourcing anti-pattern: timestamps now stored in events, not UtcNow in Apply
- Add [Authorize] to BankingController OAuth callback
- Add company access check on attachment downloads
- Validate CVR in CompanyAggregate.Create and CompanyAggregate.Update
- Require company CVR for invoice creation (Momsloven §52)
- Delete leftover WeatherForecastController
- Fix duplicate migration number 007 (renamed to 007b)
- Remove dead code in VatCalculationService (identical if/else branches)
Accounting Compliance:
- Add missing VAT accounts to StandardDanishAccounts (5610, 5611, 5620)
- Populate SAF-T TaxInformation on transaction lines (was always null)
- Add AuditFileCountry and TaxRegistrationNumber to SAF-T header
Critical Frontend Bugs:
- Fix Dashboard <a href> causing full page reloads (now uses React Router Link)
- Wire Kassekladde filters to actual data (account, status, date range)
- Pre-populate form when editing existing Kassekladde drafts
- Add detail drawer for "Vis detaljer" action (was just a toast)
- Toggle advanced filters with "Flere filtre" button
- CloseFiscalYearWizard now actually posts closing entries via mutations
- "Create next year" checkbox now creates the next fiscal year
Danish Character Encoding (~50 fixes):
- Fix ø/æ/å across Momsindberetning, DocumentUploadModal, Bankafstemning,
Kontooversigt, CloseFiscalYearWizard, vatCodes, periodStore, periods,
accounting, types/periods
Dead Buttons & UX:
- Disable Momsindberetning PDF/Export buttons with tooltips
- FiscalYearSelector "Administrer" now navigates to Settings
- Settings bank tab now uses real BankConnectionsTab component
- Bankafstemning save button disabled with development tooltip
- Replace hardcoded account options with real API data (Bankafstemning, Fakturaer)
- Header help button shows info message, notification bell shows popover
Consistency & Quality:
- Remove 7 console.log statements from production code
- Adopt PageHeader on 6 remaining pages (Kreditnotaer, Settings, Admin, etc.)
- Standardize loading states to Skeleton pattern (5 pages)
- Replace deprecated bodyStyle prop on Ant Design Cards
- Standardize date format to DD-MM-YYYY
- Fix sidebar width mismatch in designTokens
- Fix Kontooversigt breadcrumb pointing to non-existent route
Accessibility:
- Add aria-label to sidebar navigation
- Add +/- prefix to AmountText for color-blind users
- Fix CompanySwitcher permanent skeleton when no companies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 00:18:19 +01:00
|
|
|
const sign = alwaysSign && amount !== 0 ? (amount > 0 ? '+' : '-') : amount < 0 ? '-' : '';
|
Add frontend components, API mutations, and project config
Frontend:
- API mutations for accounts, bank connections, customers, invoices
- Document processing API
- Shared components (PageHeader, EmptyState, etc.)
- Pages: Admin, Fakturaer, Kunder, Ordrer, Produkter, etc.
- Hooks and stores
Config:
- CLAUDE.md project instructions
- Beads issue tracking config
- Git attributes
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 22:20:03 +01:00
|
|
|
const suffix = showCurrency ? ` ${currencySuffix}` : '';
|
|
|
|
|
|
|
|
|
|
return `${sign}${formatted}${suffix}`;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Determine icon for accessibility (not relying on color alone)
|
|
|
|
|
const getIcon = () => {
|
|
|
|
|
if (!showIcon) return null;
|
|
|
|
|
|
|
|
|
|
const effectiveType = type === 'auto'
|
|
|
|
|
? (amount > 0 ? 'credit' : amount < 0 ? 'debit' : 'neutral')
|
|
|
|
|
: type;
|
|
|
|
|
|
|
|
|
|
const iconStyle = { marginRight: 4, fontSize: getFontSize() - 2 };
|
|
|
|
|
|
|
|
|
|
switch (effectiveType) {
|
|
|
|
|
case 'credit':
|
|
|
|
|
return <ArrowUpOutlined style={iconStyle} aria-label="Kredit" />;
|
|
|
|
|
case 'debit':
|
|
|
|
|
return <ArrowDownOutlined style={iconStyle} aria-label="Debet" />;
|
|
|
|
|
default:
|
|
|
|
|
return <MinusOutlined style={iconStyle} aria-label="Nul" />;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const textStyle: React.CSSProperties = {
|
|
|
|
|
color: getColor(),
|
|
|
|
|
fontSize: getFontSize(),
|
|
|
|
|
fontVariantNumeric: tabular ? 'tabular-nums' : undefined,
|
|
|
|
|
...style,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const content = (
|
|
|
|
|
<Text className={className} style={textStyle}>
|
|
|
|
|
{getIcon()}
|
|
|
|
|
{formatAmount()}
|
|
|
|
|
</Text>
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (showTooltip) {
|
|
|
|
|
return (
|
|
|
|
|
<Tooltip title={`${amount.toLocaleString('da-DK', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ${currencySuffix}`}>
|
|
|
|
|
{content}
|
|
|
|
|
</Tooltip>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Displays a balance with label
|
|
|
|
|
*/
|
|
|
|
|
export interface BalanceDisplayProps {
|
|
|
|
|
label: string;
|
|
|
|
|
amount: number;
|
|
|
|
|
showChange?: boolean;
|
|
|
|
|
change?: number;
|
|
|
|
|
size?: 'small' | 'default' | 'large';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function BalanceDisplay({ label, amount, size = 'default' }: BalanceDisplayProps) {
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<Text type="secondary" style={{ fontSize: typography.caption.fontSize }}>
|
|
|
|
|
{label}
|
|
|
|
|
</Text>
|
|
|
|
|
<div>
|
|
|
|
|
<AmountText amount={amount} size={size} />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Displays debit and credit columns for double-entry bookkeeping
|
|
|
|
|
*/
|
|
|
|
|
export interface DoubleEntryAmountProps {
|
|
|
|
|
debit?: number;
|
|
|
|
|
credit?: number;
|
|
|
|
|
showZero?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function DoubleEntryAmount({ debit, credit, showZero = false }: DoubleEntryAmountProps) {
|
|
|
|
|
const showDebit = debit !== undefined && (debit !== 0 || showZero);
|
|
|
|
|
const showCredit = credit !== undefined && (credit !== 0 || showZero);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<span style={{ display: 'inline-block', minWidth: 100, textAlign: 'right' }}>
|
|
|
|
|
{showDebit && <AmountText amount={debit!} type="debit" showCurrency={false} />}
|
|
|
|
|
</span>
|
|
|
|
|
<span style={{ display: 'inline-block', minWidth: 100, textAlign: 'right', marginLeft: 16 }}>
|
|
|
|
|
{showCredit && <AmountText amount={credit!} type="credit" showCurrency={false} />}
|
|
|
|
|
</span>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Displays a running balance with optional delta indicator
|
|
|
|
|
*/
|
|
|
|
|
export interface RunningBalanceProps {
|
|
|
|
|
balance: number;
|
|
|
|
|
previousBalance?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function RunningBalance({ balance, previousBalance }: RunningBalanceProps) {
|
|
|
|
|
const delta = previousBalance !== undefined ? balance - previousBalance : undefined;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<span className="tabular-nums">
|
|
|
|
|
<AmountText amount={balance} type="auto" />
|
|
|
|
|
{delta !== undefined && delta !== 0 && (
|
|
|
|
|
<Text
|
|
|
|
|
type="secondary"
|
|
|
|
|
style={{
|
|
|
|
|
fontSize: typography.small.fontSize,
|
|
|
|
|
marginLeft: 4,
|
|
|
|
|
color: delta > 0 ? accountingColors.credit : accountingColors.debit,
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
({delta > 0 ? '+' : ''}
|
|
|
|
|
{formatCurrency(delta)})
|
|
|
|
|
</Text>
|
|
|
|
|
)}
|
|
|
|
|
</span>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default AmountText;
|