Improve Kontooversigt UX and add missing CVR validation
UX improvements (books-8lo): - Use PageHeader component for consistent header with breadcrumbs - Add responsive mobile breakpoints - Improve accessibility with aria-labels - Better information hierarchy Fixes (books-1rp): - Add missing validateCVRModulus11 function to formatters - Fixes TypeScript errors in Kunder.tsx and CompanySetupWizard.tsx Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
49971b3265
commit
8bf3141ba3
3 changed files with 89 additions and 54 deletions
|
|
@ -1,11 +1,11 @@
|
|||
{"id":"books-1rp","title":"http://localhost:3000/kunder","status":"open","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:30:29.369137+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:30:29.369137+01:00"}
|
||||
{"id":"books-1rp","title":"http://localhost:3000/kunder","status":"in_progress","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:30:29.369137+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:45:00.573047+01:00"}
|
||||
{"id":"books-5tg","title":"opret et backend job med hangfir\ne som sikrer, at bankkonto altid stemmer med den pågældende konto","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:24:01.505911+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:37:08.293897+01:00","closed_at":"2026-01-30T14:37:08.293897+01:00","close_reason":"Closed"}
|
||||
{"id":"books-8ea","title":"fjern brugers navn fra højre hjørne ved profile ikonet","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:20:16.406033+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:22:59.64468+01:00","closed_at":"2026-01-30T14:22:59.64468+01:00","close_reason":"Closed"}
|
||||
{"id":"books-8lo","title":"revisit the laytoug and desig nfor kontooversigten.","status":"open","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:25:06.620288+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:25:06.620288+01:00"}
|
||||
{"id":"books-8lo","title":"revisit the laytoug and desig nfor kontooversigten.","status":"in_progress","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:25:06.620288+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:45:00.448995+01:00"}
|
||||
{"id":"books-bj6","title":"Test automatisk pickup","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:04:40.572496+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:05:44.401903+01:00","closed_at":"2026-01-30T14:05:44.401903+01:00","close_reason":"completed"}
|
||||
{"id":"books-byl","title":"opret giv et shortlink på frontenden til backend på /hangfire","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:24:34.946139+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:40:44.62014+01:00","closed_at":"2026-01-30T14:40:44.62014+01:00","close_reason":"Closed"}
|
||||
{"id":"books-cdf","title":"opret","status":"open","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:23:39.411558+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:23:39.411558+01:00"}
|
||||
{"id":"books-ced","title":"brug smb om regnskab + fropntend designer til at sikrer at alt er godt for både balance og kontooversigt","status":"open","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:25:46.484629+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:25:46.484629+01:00"}
|
||||
{"id":"books-ced","title":"brug smb om regnskab + fropntend designer til at sikrer at alt er godt for både balance og kontooversigt","status":"in_progress","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:25:46.484629+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:45:00.511206+01:00"}
|
||||
{"id":"books-h6e","title":"fjern hurtig bogføring og den visning der høre dertil","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:14:50.436314+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:18:09.911294+01:00","closed_at":"2026-01-30T14:18:09.911294+01:00","close_reason":"Closed"}
|
||||
{"id":"books-hzt","title":"fix bug med tilføj brugere står forkert med encoded tegn","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:21:34.556319+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:28:31.320973+01:00","closed_at":"2026-01-30T14:28:31.320973+01:00","close_reason":"Closed"}
|
||||
{"id":"books-sbm","title":"ændre navnet i venstre side til Books","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:11:13.017202+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:12:14.16594+01:00","closed_at":"2026-01-30T14:12:14.16594+01:00","close_reason":"Closed"}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,28 @@ export function formatCVR(cvr: string): string {
|
|||
return `${cleaned.slice(0, 2)} ${cleaned.slice(2, 4)} ${cleaned.slice(4, 6)} ${cleaned.slice(6, 8)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a Danish CVR number using the modulus 11 algorithm
|
||||
* @param cvr - 8-digit CVR number as string
|
||||
* @returns true if valid, false otherwise
|
||||
*/
|
||||
export function validateCVRModulus11(cvr: string): boolean {
|
||||
const cleaned = cvr.replace(/\D/g, '');
|
||||
if (cleaned.length !== 8) return false;
|
||||
|
||||
// Weights for each position in the CVR number
|
||||
const weights = [2, 7, 6, 5, 4, 3, 2, 1];
|
||||
|
||||
// Calculate weighted sum
|
||||
let sum = 0;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
sum += parseInt(cleaned[i], 10) * weights[i];
|
||||
}
|
||||
|
||||
// Valid if sum is divisible by 11
|
||||
return sum % 11 === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSS class for amount (positive/negative/zero)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
Tabs,
|
||||
Statistic,
|
||||
message,
|
||||
Grid,
|
||||
} from 'antd';
|
||||
import {
|
||||
PlusOutlined,
|
||||
|
|
@ -29,9 +30,13 @@ import { useCompany } from '@/hooks/useCompany';
|
|||
import { formatCurrency, formatDate } from '@/lib/formatters';
|
||||
import { getAccountTypeName, getAccountNumberRange } from '@/lib/accounting';
|
||||
import { accountingColors } from '@/styles/theme';
|
||||
import { spacing } from '@/styles/designTokens';
|
||||
import { PageHeader } from '@/components/shared/PageHeader';
|
||||
import { EmptyState } from '@/components/shared/EmptyState';
|
||||
import type { Account, AccountType } from '@/types/accounting';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Text } = Typography;
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
// Mock data
|
||||
const mockAccounts: Account[] = [
|
||||
|
|
@ -71,12 +76,15 @@ const accountTypes: AccountType[] = [
|
|||
|
||||
export default function Kontooversigt() {
|
||||
const { company } = useCompany();
|
||||
const screens = useBreakpoint();
|
||||
const [selectedAccount, setSelectedAccount] = useState<Account | null>(null);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [editingAccount, setEditingAccount] = useState<Account | null>(null);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const isMobile = !screens.md;
|
||||
|
||||
// Build tree data from accounts
|
||||
const buildTreeData = (): DataNode[] => {
|
||||
return accountTypes.map((type) => {
|
||||
|
|
@ -235,29 +243,29 @@ export default function Kontooversigt() {
|
|||
return (
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
<PageHeader
|
||||
title="Kontooversigt"
|
||||
subtitle={company?.name}
|
||||
breadcrumbs={[
|
||||
{ title: 'Bogforing', path: '/bogforing' },
|
||||
{ title: 'Kontooversigt' },
|
||||
]}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={handleCreateAccount}
|
||||
aria-label="Opret ny konto"
|
||||
>
|
||||
<div>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
Kontooversigt
|
||||
</Title>
|
||||
<Text type="secondary">{company?.name}</Text>
|
||||
</div>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={handleCreateAccount}>
|
||||
Ny konto
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Summary Cards */}
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Row gutter={[spacing.lg, spacing.lg]} style={{ marginBottom: spacing.lg }}>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card size="small">
|
||||
<Card size="small" role="region" aria-label="Aktiver total">
|
||||
<Statistic
|
||||
title="Aktiver"
|
||||
value={totalAssets}
|
||||
|
|
@ -269,7 +277,7 @@ export default function Kontooversigt() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card size="small">
|
||||
<Card size="small" role="region" aria-label="Passiver total">
|
||||
<Statistic
|
||||
title="Passiver"
|
||||
value={totalLiabilities}
|
||||
|
|
@ -281,9 +289,9 @@ export default function Kontooversigt() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card size="small">
|
||||
<Card size="small" role="region" aria-label="Omsaetning total">
|
||||
<Statistic
|
||||
title="Omsætning"
|
||||
title="Omsaetning"
|
||||
value={totalRevenue}
|
||||
precision={2}
|
||||
suffix="kr."
|
||||
|
|
@ -293,7 +301,7 @@ export default function Kontooversigt() {
|
|||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} sm={6}>
|
||||
<Card size="small">
|
||||
<Card size="small" role="region" aria-label="Omkostninger total">
|
||||
<Statistic
|
||||
title="Omkostninger"
|
||||
value={totalExpenses}
|
||||
|
|
@ -307,30 +315,31 @@ export default function Kontooversigt() {
|
|||
</Row>
|
||||
|
||||
{/* Main Content */}
|
||||
<Row gutter={16}>
|
||||
<Row gutter={spacing.lg}>
|
||||
{/* Account Tree */}
|
||||
<Col xs={24} lg={10}>
|
||||
<Card
|
||||
title="Kontoplan"
|
||||
size="small"
|
||||
extra={
|
||||
<Card title="Kontoplan" size="small">
|
||||
{/* Search moved outside extra for better mobile UX */}
|
||||
<Input
|
||||
placeholder="Søg konto..."
|
||||
placeholder="Sog efter konto eller kontonummer..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
style={{ width: 200 }}
|
||||
style={{ marginBottom: spacing.md }}
|
||||
allowClear
|
||||
aria-label="Sog i kontoplan"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Tree
|
||||
showIcon
|
||||
defaultExpandAll
|
||||
treeData={buildTreeData()}
|
||||
onSelect={handleSelectAccount}
|
||||
selectedKeys={selectedAccount ? [selectedAccount.id] : []}
|
||||
style={{ maxHeight: 500, overflow: 'auto' }}
|
||||
style={{
|
||||
maxHeight: isMobile ? 300 : 450,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
aria-label="Kontoplan hierarki"
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
|
@ -343,6 +352,9 @@ export default function Kontooversigt() {
|
|||
<Space>
|
||||
<Text code>{selectedAccount.accountNumber}</Text>
|
||||
<Text strong>{selectedAccount.name}</Text>
|
||||
{!selectedAccount.isActive && (
|
||||
<Tag color="red">Inaktiv</Tag>
|
||||
)}
|
||||
</Space>
|
||||
}
|
||||
size="small"
|
||||
|
|
@ -350,10 +362,13 @@ export default function Kontooversigt() {
|
|||
<Button
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => handleEditAccount(selectedAccount)}
|
||||
aria-label={`Rediger konto ${selectedAccount.accountNumber}`}
|
||||
>
|
||||
Rediger
|
||||
</Button>
|
||||
}
|
||||
role="region"
|
||||
aria-label={`Detaljer for konto ${selectedAccount.accountNumber}`}
|
||||
>
|
||||
<Tabs
|
||||
items={[
|
||||
|
|
@ -388,6 +403,7 @@ export default function Kontooversigt() {
|
|||
rowKey={(_, index) => String(index)}
|
||||
size="small"
|
||||
pagination={{ pageSize: 10 }}
|
||||
aria-label={`Bevaegelser for konto ${selectedAccount?.accountNumber}`}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
|
@ -435,16 +451,13 @@ export default function Kontooversigt() {
|
|||
</Card>
|
||||
) : (
|
||||
<Card size="small">
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
padding: 40,
|
||||
color: '#8c8c8c',
|
||||
}}
|
||||
>
|
||||
<FileOutlined style={{ fontSize: 48, marginBottom: 16 }} />
|
||||
<div>Vælg en konto for at se detaljer</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
variant="accounts"
|
||||
icon={<FileOutlined style={{ fontSize: 48 }} />}
|
||||
title="Ingen konto valgt"
|
||||
description="Vaelg en konto i kontoplanen til venstre for at se detaljer og bevaegelser."
|
||||
compact
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Col>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue