feat(handelsware-datev): Modulseite mit Import, Konten und Einstellungen

- Reiter Import: Vorschau mit neu-Markierung, Buchungsdatum (TTMM) aus dem Dateinamen, Download speichert neue Konten
- Reiter Konten: anlegen, bearbeiten, loeschen mit Rueckfrage, CSV-Import (ersetzt alles, nach Rueckfrage) und -Export
- Reiter Einstellungen nur fuer Administratoren; Texte de/en

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-10-02 11:47:09 +02:00
parent 44c1d4351f
commit 42b89f110b
15 changed files with 1726 additions and 1 deletions
@@ -0,0 +1,75 @@
'use client';
import { useTranslations } from 'next-intl';
import { useEffect, useState } from 'react';
import { TabBar } from '@/components/accounting/tab-bar';
import { PageHeader } from '@/components/layout/page-header';
import { getHandelswareSettings, type HandelswareSettings } from '@/lib/handelsware-datev-api';
import { useAuthStore } from '@/lib/stores/auth-store';
import { AccountsTab } from './components/AccountsTab';
import { ImportTab } from './components/ImportTab';
import { SettingsTab } from './components/SettingsTab';
type TabId = 'import' | 'accounts' | 'settings';
/**
* Handelsware (quick-261002-fm5): Excel-Umsaetze Erloeskonten zuordnen und als
* DATEV-Buchungsdatei herunterladen. Reiter Import / Konten / Einstellungen
* (letzterer nur fuer Administratoren). Nach einem Export zaehlt `accountsVersion`
* hoch, damit der Reiter "Konten" die neu gespeicherten Konten nachlaedt.
*/
export default function HandelswareDatevPage() {
const t = useTranslations('handelswareDatev');
const user = useAuthStore((s) => s.user);
const isAdmin = user?.role === 'ADMIN' || user?.role === 'SUPER_ADMIN';
const [tab, setTab] = useState<TabId>('import');
const [settings, setSettings] = useState<HandelswareSettings | null>(null);
const [accountsVersion, setAccountsVersion] = useState(0);
useEffect(() => {
let cancelled = false;
getHandelswareSettings()
.then((s) => {
if (!cancelled) setSettings(s);
})
.catch(() => {
// Ohne Einstellungen bleibt der Hinweis aus; die API sperrt trotzdem sauber.
});
return () => {
cancelled = true;
};
}, []);
const tabs: { id: TabId; label: string }[] = [
{ id: 'import', label: t('tabs.import') },
{ id: 'accounts', label: t('tabs.accounts') },
];
if (isAdmin) tabs.push({ id: 'settings', label: t('tabs.settings') });
const activeTab = tab === 'settings' && !isAdmin ? 'import' : tab;
return (
<div className="mx-auto max-w-5xl space-y-6 p-3 sm:p-6">
<PageHeader
moduleSlug="handelsware-datev"
title={t('title')}
description={t('description')}
/>
<TabBar tabs={tabs} active={activeTab} onChange={setTab} />
<div className="rounded-lg bg-card p-6 shadow-sm dark:border dark:border-border">
{activeTab === 'import' && (
<ImportTab
settings={settings}
isAdmin={isAdmin}
onOpenSettings={() => setTab('settings')}
onAccountsChanged={() => setAccountsVersion((v) => v + 1)}
/>
)}
{activeTab === 'accounts' && (
<AccountsTab settings={settings} reloadKey={accountsVersion} />
)}
{activeTab === 'settings' && <SettingsTab settings={settings} onSaved={setSettings} />}
</div>
</div>
);
}