From 7d819ace2808d4d433654d50c4cf6034edbc7fd3 Mon Sep 17 00:00:00 2001 From: Nicolaj Hartmann Date: Fri, 30 Jan 2026 22:42:00 +0100 Subject: [PATCH] Remove mock data and connect frontend to backend GraphQL - CompanySwitcher: Use useMyCompanies() hook instead of mockCompanies - FiscalYearSelector: Use useFiscalYears() hook instead of mockFiscalYears - Kontooversigt: Use useAccounts() and useAccountBalances() hooks - Kassekladde: Use useActiveAccounts() and useJournalEntryDrafts() hooks - Bankafstemning: Use useActiveBankConnections() and usePendingBankTransactions() - Dashboard: Calculate metrics from useAccountBalances(), useInvoices(), useVatReport() All components now show loading skeletons and empty states appropriately. Closes books-ljg Co-Authored-By: Claude Opus 4.5 --- .beads/issues.jsonl | 1 + .../src/components/layout/CompanySwitcher.tsx | 59 +- .../components/layout/FiscalYearSelector.tsx | 70 +-- frontend/src/pages/Bankafstemning.tsx | 552 +++++++++--------- frontend/src/pages/Dashboard.tsx | 326 +++++++---- frontend/src/pages/Kassekladde.tsx | 199 +++---- frontend/src/pages/Kontooversigt.tsx | 194 +++--- 7 files changed, 706 insertions(+), 695 deletions(-) diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index d71c8e6..8cb8c16 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -9,6 +9,7 @@ {"id":"books-ced","title":"brug smb om regnskab + fropntend designer til at sikrer at alt er godt for både balance og kontooversigt","status":"closed","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:47:52.42433+01:00","closed_at":"2026-01-30T14:47:52.42433+01:00","close_reason":"Closed"} {"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-ljg","title":"Fjern mock data og kobl frontend til backend GraphQL","description":"Frontend bruger ~2000 linjer hardcoded mock data i stedet for at bruge de eksisterende GraphQL hooks.\n\n## Problem\n- Backend GraphQL API er klar med queries og mutations\n- Frontend har hooks skrevet (useAccounts, useFiscalYears, etc.)\n- Men pages bruger hardcoded mock data i stedet for at kalde hooks\n\n## Filer der skal opdateres\n1. Dashboard.tsx - mock metrics, charts, transactions\n2. Kassekladde.tsx - mock accounts og posteringer \n3. Kontooversigt.tsx - mock kontoplan og balancer\n4. Bankafstemning.tsx - mock bank accounts og transaktioner\n5. FiscalYearSelector.tsx - mock fiscal years\n6. CompanySwitcher.tsx - mock companies\n7. Stores (companyStore, periodStore) - skal initialiseres fra API\n\n## Acceptkriterier\n- Al mock data fjernet fra frontend\n- Alle pages bruger GraphQL hooks til at hente data\n- Stores initialiseres korrekt ved app start\n- Data vises fra backend i UI","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T22:27:49.225279+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T22:42:04.17437+01:00","closed_at":"2026-01-30T22:42:04.17437+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"} {"id":"books-wqf","title":"Opret en logud knap i topbaren","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:06:06.999508+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:10:52.860045+01:00","closed_at":"2026-01-30T14:10:52.860045+01:00","close_reason":"Closed"} {"id":"books-wzq","title":"tilføj en lille disclaimer på alle områder, hvor der er statisk data. brug gerne planning mode","status":"closed","priority":2,"issue_type":"task","owner":"nhh@softwarehuset.com","created_at":"2026-01-30T14:22:53.728536+01:00","created_by":"Nicolaj Hartmann","updated_at":"2026-01-30T14:40:44.557962+01:00","closed_at":"2026-01-30T14:40:44.557962+01:00","close_reason":"Closed"} diff --git a/frontend/src/components/layout/CompanySwitcher.tsx b/frontend/src/components/layout/CompanySwitcher.tsx index 86ec65e..896a493 100644 --- a/frontend/src/components/layout/CompanySwitcher.tsx +++ b/frontend/src/components/layout/CompanySwitcher.tsx @@ -1,6 +1,8 @@ -import { Select, Space, Typography, Tag } from 'antd'; +import { useEffect } from 'react'; +import { Select, Space, Typography, Tag, Skeleton } from 'antd'; import { ShopOutlined } from '@ant-design/icons'; import { useCompanyStore } from '@/stores/companyStore'; +import { useMyCompanies } from '@/api/queries/companyQueries'; import { formatCVR } from '@/lib/formatters'; import type { Company } from '@/types/accounting'; @@ -10,48 +12,19 @@ interface CompanySwitcherProps { compact?: boolean; } -// Mock data - will be replaced with API call -const mockCompanies: Company[] = [ - { - id: '1', - name: 'Demo Virksomhed ApS', - cvr: '12345678', - address: 'Hovedgaden 1', - city: 'Koebenhavn', - postalCode: '1000', - country: 'DK', - fiscalYearStart: 1, - currency: 'DKK', - createdAt: '2024-01-01', - updatedAt: '2024-01-01', - }, - { - id: '2', - name: 'Anden Virksomhed A/S', - cvr: '87654321', - address: 'Sidegaden 2', - city: 'Aarhus', - postalCode: '8000', - country: 'DK', - fiscalYearStart: 7, - currency: 'DKK', - createdAt: '2024-01-01', - updatedAt: '2024-01-01', - }, -]; - export default function CompanySwitcher({ compact = false }: CompanySwitcherProps) { + const { data: companies = [], isLoading } = useMyCompanies(); const { activeCompany, setActiveCompany, setCompanies } = useCompanyStore(); - // Initialize with mock data if needed - if (useCompanyStore.getState().companies.length === 0) { - setCompanies(mockCompanies); - if (!activeCompany) { - setActiveCompany(mockCompanies[0]); + // Sync companies with store when data changes + useEffect(() => { + if (companies.length > 0) { + setCompanies(companies); + if (!activeCompany) { + setActiveCompany(companies[0]); + } } - } - - const companies = useCompanyStore((state) => state.companies); + }, [companies, activeCompany, setActiveCompany, setCompanies]); const handleCompanyChange = (companyId: string) => { const company = companies.find((c) => c.id === companyId); @@ -60,6 +33,14 @@ export default function CompanySwitcher({ compact = false }: CompanySwitcherProp } }; + if (isLoading) { + return ; + } + + if (companies.length === 0) { + return null; + } + return ( diff --git a/frontend/src/components/layout/FiscalYearSelector.tsx b/frontend/src/components/layout/FiscalYearSelector.tsx index 85eaef4..730a80e 100644 --- a/frontend/src/components/layout/FiscalYearSelector.tsx +++ b/frontend/src/components/layout/FiscalYearSelector.tsx @@ -1,7 +1,7 @@ -// FiscalYearSelector - Dropdown for selecting active fiscal year (regnskabsår) +// FiscalYearSelector - Dropdown for selecting active fiscal year (regnskabsar) import { useState, useEffect } from 'react'; -import { Select, Space, Typography, Tag, Divider, Button } from 'antd'; +import { Select, Space, Typography, Tag, Divider, Button, Skeleton } from 'antd'; import { CalendarOutlined, PlusOutlined, @@ -11,53 +11,14 @@ import { LockOutlined, } from '@ant-design/icons'; import { usePeriodStore } from '@/stores/periodStore'; +import { useCompanyStore } from '@/stores/companyStore'; +import { useFiscalYears } from '@/api/queries/fiscalYearQueries'; import type { FiscalYear } from '@/types/periods'; import { formatDateShort } from '@/lib/formatters'; import CreateFiscalYearModal from '@/components/modals/CreateFiscalYearModal'; const { Text } = Typography; -// Mock data - will be replaced with API call -const mockFiscalYears: FiscalYear[] = [ - { - id: 'fy-2025', - companyId: '1', - name: '2025', - startDate: '2025-01-01', - endDate: '2025-12-31', - status: 'open', - openingBalancePosted: true, - createdAt: '2025-01-01', - updatedAt: '2025-01-01', - }, - { - id: 'fy-2024', - companyId: '1', - name: '2024', - startDate: '2024-01-01', - endDate: '2024-12-31', - status: 'closed', - closingDate: '2025-01-15', - closedBy: 'user-1', - openingBalancePosted: true, - createdAt: '2024-01-01', - updatedAt: '2025-01-15', - }, - { - id: 'fy-2023', - companyId: '1', - name: '2023', - startDate: '2023-01-01', - endDate: '2023-12-31', - status: 'locked', - closingDate: '2024-01-20', - closedBy: 'user-1', - openingBalancePosted: true, - createdAt: '2023-01-01', - updatedAt: '2024-01-20', - }, -]; - /** * Status badge configuration */ @@ -69,7 +30,7 @@ const STATUS_CONFIG: Record, - label: 'Åben', + label: 'Aben', }, closed: { color: 'warning', @@ -79,7 +40,7 @@ const STATUS_CONFIG: Record, - label: 'Låst', + label: 'Last', }, }; @@ -89,6 +50,7 @@ interface FiscalYearSelectorProps { } export default function FiscalYearSelector({ onCreateNew, onManage }: FiscalYearSelectorProps) { + const { activeCompany } = useCompanyStore(); const { fiscalYears, currentFiscalYear, @@ -96,14 +58,16 @@ export default function FiscalYearSelector({ onCreateNew, onManage }: FiscalYear setCurrentFiscalYear, } = usePeriodStore(); + const { data: fiscalYearsData = [], isLoading } = useFiscalYears(activeCompany?.id); + const [createModalOpen, setCreateModalOpen] = useState(false); - // Initialize with mock data if needed (will be replaced with API call) + // Sync fiscal years with store when data changes useEffect(() => { - if (fiscalYears.length === 0) { - setFiscalYears(mockFiscalYears); + if (fiscalYearsData.length > 0) { + setFiscalYears(fiscalYearsData); } - }, [fiscalYears.length, setFiscalYears]); + }, [fiscalYearsData, setFiscalYears]); // Set default fiscal year if none selected useEffect(() => { @@ -147,6 +111,10 @@ export default function FiscalYearSelector({ onCreateNew, onManage }: FiscalYear } }; + if (isLoading) { + return ; + } + // Sort fiscal years by start date descending (newest first) const sortedYears = [...fiscalYears].sort( (a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime() @@ -172,7 +140,7 @@ export default function FiscalYearSelector({ onCreateNew, onManage }: FiscalYear onClick={handleCreateNew} size="small" > - Opret nyt regnskabsår + Opret nyt regnskabsar )} @@ -497,7 +478,7 @@ export default function Kassekladde() { (null); const [isModalOpen, setIsModalOpen] = useState(false); @@ -85,11 +66,29 @@ export default function Kontooversigt() { const isMobile = !screens.md; + // Fetch accounts and balances from API + const { data: accounts = [], isLoading: accountsLoading } = useAccounts(activeCompany?.id); + const { data: balances = [], isLoading: balancesLoading } = useAccountBalances( + activeCompany?.id, + currentFiscalYear ? { + startDate: dayjs(currentFiscalYear.startDate), + endDate: dayjs(currentFiscalYear.endDate), + } : undefined + ); + + const isLoading = accountsLoading || balancesLoading; + + // Combine accounts with balances + const accountsWithBalances = accounts.map(acc => { + const balance = balances.find(b => b.id === acc.id); + return { ...acc, balance: balance?.netChange ?? 0 }; + }); + // Build tree data from accounts const buildTreeData = (): DataNode[] => { return accountTypes.map((type) => { const range = getAccountNumberRange(type); - const typeAccounts = mockAccounts.filter((acc) => acc.type === type); + const typeAccounts = accountsWithBalances.filter((acc) => acc.type === type); const typeBalance = typeAccounts.reduce((sum, acc) => sum + acc.balance, 0); return { @@ -142,7 +141,7 @@ export default function Kontooversigt() { const handleSelectAccount = (selectedKeys: React.Key[]) => { const key = selectedKeys[0]; if (key && !accountTypes.includes(key as AccountType)) { - const account = mockAccounts.find((acc) => acc.id === key); + const account = accountsWithBalances.find((acc) => acc.id === key); setSelectedAccount(account || null); } }; @@ -170,82 +169,68 @@ export default function Kontooversigt() { } }; - // Account transactions for selected account - const accountTransactions = selectedAccount - ? mockTransactions.filter((tx) => tx.accountId === selectedAccount.id) - : []; - - const transactionColumns = [ - { - dataIndex: 'date', - title: 'Dato', - width: 100, - render: (value: string) => formatDate(value), - }, - { - dataIndex: 'description', - title: 'Beskrivelse', - ellipsis: true, - }, - { - dataIndex: 'debit', - title: 'Debet', - width: 120, - align: 'right' as const, - render: (value: number) => - value > 0 ? ( - {formatCurrency(value)} - ) : null, - }, - { - dataIndex: 'credit', - title: 'Kredit', - width: 120, - align: 'right' as const, - render: (value: number) => - value > 0 ? ( - {formatCurrency(value)} - ) : null, - }, - { - dataIndex: 'balance', - title: 'Saldo', - width: 120, - align: 'right' as const, - render: (value: number) => ( - = 0 ? accountingColors.credit : accountingColors.debit, - }} - > - {formatCurrency(value)} - - ), - }, - ]; - - // Calculate totals - const totalAssets = mockAccounts + // Calculate totals from actual data + const totalAssets = accountsWithBalances .filter((a) => a.type === 'asset') .reduce((sum, a) => sum + a.balance, 0); - const totalLiabilities = mockAccounts + const totalLiabilities = accountsWithBalances .filter((a) => ['liability', 'equity'].includes(a.type)) .reduce((sum, a) => sum + Math.abs(a.balance), 0); - const totalRevenue = mockAccounts + const totalRevenue = accountsWithBalances .filter((a) => a.type === 'revenue') .reduce((sum, a) => sum + Math.abs(a.balance), 0); - const totalExpenses = mockAccounts + const totalExpenses = accountsWithBalances .filter((a) => ['cogs', 'expense', 'personnel', 'financial'].includes(a.type)) .reduce((sum, a) => sum + a.balance, 0); + if (isLoading) { + return ( +
+ + +
+ ); + } + + if (accounts.length === 0) { + return ( +
+ } + onClick={handleCreateAccount} + aria-label="Opret ny konto" + > + Ny konto + + } + /> + +
+ ); + } + return (
{/* Header */}
- String(index)} - size="small" - pagination={{ pageSize: 10 }} - aria-label={`Bevaegelser for konto ${selectedAccount?.accountNumber}`} - /> + ), }, @@ -480,7 +458,7 @@ export default function Kontooversigt() { { required: true, message: 'Indtast kontonummer' }, { pattern: /^\d{4}$/, - message: 'Kontonummer skal være 4 cifre', + message: 'Kontonummer skal vaere 4 cifre', }, ]} > @@ -498,10 +476,10 @@ export default function Kontooversigt() {