books/frontend/src/components/layout/FiscalYearSelector.tsx
Nicolaj Hartmann 7d819ace28 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 <noreply@anthropic.com>
2026-01-30 22:42:10 +01:00

210 lines
5.9 KiB
TypeScript

// FiscalYearSelector - Dropdown for selecting active fiscal year (regnskabsar)
import { useState, useEffect } from 'react';
import { Select, Space, Typography, Tag, Divider, Button, Skeleton } from 'antd';
import {
CalendarOutlined,
PlusOutlined,
SettingOutlined,
CheckCircleOutlined,
MinusCircleOutlined,
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;
/**
* Status badge configuration
*/
const STATUS_CONFIG: Record<FiscalYear['status'], {
color: string;
icon: React.ReactNode;
label: string;
}> = {
open: {
color: 'success',
icon: <CheckCircleOutlined />,
label: 'Aben',
},
closed: {
color: 'warning',
icon: <MinusCircleOutlined />,
label: 'Lukket',
},
locked: {
color: 'error',
icon: <LockOutlined />,
label: 'Last',
},
};
interface FiscalYearSelectorProps {
onCreateNew?: () => void;
onManage?: () => void;
}
export default function FiscalYearSelector({ onCreateNew, onManage }: FiscalYearSelectorProps) {
const { activeCompany } = useCompanyStore();
const {
fiscalYears,
currentFiscalYear,
setFiscalYears,
setCurrentFiscalYear,
} = usePeriodStore();
const { data: fiscalYearsData = [], isLoading } = useFiscalYears(activeCompany?.id);
const [createModalOpen, setCreateModalOpen] = useState(false);
// Sync fiscal years with store when data changes
useEffect(() => {
if (fiscalYearsData.length > 0) {
setFiscalYears(fiscalYearsData);
}
}, [fiscalYearsData, setFiscalYears]);
// Set default fiscal year if none selected
useEffect(() => {
if (fiscalYears.length > 0 && !currentFiscalYear) {
// Default to most recent open year, or first year
const openYear = fiscalYears.find(y => y.status === 'open');
setCurrentFiscalYear(openYear || fiscalYears[0]);
}
}, [fiscalYears, currentFiscalYear, setCurrentFiscalYear]);
const handleFiscalYearChange = (yearId: string) => {
const year = fiscalYears.find((y) => y.id === yearId);
if (year) {
setCurrentFiscalYear(year);
}
};
const handleCreateNew = () => {
if (onCreateNew) {
onCreateNew();
} else {
setCreateModalOpen(true);
}
};
const handleCloseCreateModal = () => {
setCreateModalOpen(false);
};
const handleCreateSuccess = (newYear: FiscalYear) => {
setCurrentFiscalYear(newYear);
setCreateModalOpen(false);
};
const handleManage = () => {
if (onManage) {
onManage();
} else {
// Navigate to settings page
console.log('Navigate to fiscal year settings');
}
};
if (isLoading) {
return <Skeleton.Input style={{ width: 200 }} active />;
}
// 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()
);
return (
<Space>
<CalendarOutlined style={{ fontSize: 16, color: '#8c8c8c' }} />
<Select
value={currentFiscalYear?.id}
onChange={handleFiscalYearChange}
style={{ minWidth: 200 }}
optionLabelProp="label"
popupMatchSelectWidth={false}
dropdownRender={(menu) => (
<>
{menu}
<Divider style={{ margin: '8px 0' }} />
<Space style={{ padding: '0 8px 8px' }}>
<Button
type="text"
icon={<PlusOutlined />}
onClick={handleCreateNew}
size="small"
>
Opret nyt regnskabsar
</Button>
<Button
type="text"
icon={<SettingOutlined />}
onClick={handleManage}
size="small"
>
Administrer
</Button>
</Space>
</>
)}
options={sortedYears.map((year) => ({
value: year.id,
label: `Regnskabsar ${year.name}`,
year,
}))}
optionRender={(option) => {
const year = option.data.year;
// Type guard - ensure year exists and has required properties
if (!year || typeof year !== 'object' || !('status' in year)) {
return null;
}
const fiscalYear = year as FiscalYear;
const statusConfig = STATUS_CONFIG[fiscalYear.status];
return (
<Space
direction="vertical"
size={0}
style={{ padding: '4px 0', width: '100%' }}
>
<Space style={{ width: '100%', justifyContent: 'space-between' }}>
<Text strong>{fiscalYear.name}</Text>
<Tag
color={statusConfig.color}
icon={statusConfig.icon}
style={{ marginLeft: 8 }}
>
{statusConfig.label}
</Tag>
</Space>
<Text type="secondary" style={{ fontSize: 12 }}>
{formatDateShort(fiscalYear.startDate)} - {formatDateShort(fiscalYear.endDate)}
</Text>
</Space>
);
}}
/>
{currentFiscalYear && (
<Tag
color={STATUS_CONFIG[currentFiscalYear.status].color}
icon={STATUS_CONFIG[currentFiscalYear.status].icon}
>
{STATUS_CONFIG[currentFiscalYear.status].label}
</Tag>
)}
{/* Create Fiscal Year Modal */}
<CreateFiscalYearModal
open={createModalOpen}
onClose={handleCloseCreateModal}
onSuccess={handleCreateSuccess}
/>
</Space>
);
}