185 lines
7 KiB
TypeScript
185 lines
7 KiB
TypeScript
/**
|
|
* TrusteeDashboardView
|
|
*
|
|
* Overview dashboard for a Trustee instance.
|
|
* Shows statistics about positions, documents, and accounting sync status.
|
|
* Includes a QuickActionBoard for one-click navigation to feature pages.
|
|
*/
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import { useCurrentInstance } from '../../../hooks/useCurrentInstance';
|
|
import { useTrusteePositions, useTrusteeDocuments } from '../../../hooks/useTrustee';
|
|
import { useApiRequest } from '../../../hooks/useApi';
|
|
import {
|
|
fetchAccountingConfig,
|
|
fetchSyncStatus,
|
|
fetchQuickActions,
|
|
type AccountingConfig,
|
|
type AccountingSyncStatus,
|
|
} from '../../../api/trusteeApi';
|
|
import { QuickActionBoard, type QuickAction, type QuickActionCategory } from '../../../components/QuickActionBoard';
|
|
import styles from './TrusteeViews.module.css';
|
|
|
|
import { useLanguage } from '../../../providers/language/LanguageContext';
|
|
|
|
export const TrusteeDashboardView: React.FC = () => {
|
|
const { t, currentLanguage } = useLanguage();
|
|
const navigate = useNavigate();
|
|
const { mandateId } = useParams<{ mandateId: string }>();
|
|
|
|
const { mandate, instance, instanceId } = useCurrentInstance();
|
|
const { items: positions, loading: posLoading } = useTrusteePositions();
|
|
const { items: documents, loading: docsLoading } = useTrusteeDocuments();
|
|
const { request } = useApiRequest();
|
|
|
|
const [accountingConfig, setAccountingConfig] = useState<AccountingConfig | null>(null);
|
|
const [syncItems, setSyncItems] = useState<AccountingSyncStatus[]>([]);
|
|
const [accountingLoading, setAccountingLoading] = useState(true);
|
|
const [quickActions, setQuickActions] = useState<QuickAction[]>([]);
|
|
const [quickActionCategories, setQuickActionCategories] = useState<QuickActionCategory[]>([]);
|
|
const [quickActionsLoading, setQuickActionsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (!instanceId) return;
|
|
const _loadAccountingData = async () => {
|
|
setAccountingLoading(true);
|
|
try {
|
|
const [config, syncData] = await Promise.all([
|
|
fetchAccountingConfig(request, instanceId),
|
|
fetchSyncStatus(request, instanceId),
|
|
]);
|
|
setAccountingConfig(config);
|
|
setSyncItems(syncData?.items || []);
|
|
} catch {
|
|
// Accounting not configured is fine
|
|
} finally {
|
|
setAccountingLoading(false);
|
|
}
|
|
};
|
|
_loadAccountingData();
|
|
}, [instanceId, request]);
|
|
|
|
useEffect(() => {
|
|
if (!instanceId) return;
|
|
const _loadQuickActions = async () => {
|
|
setQuickActionsLoading(true);
|
|
try {
|
|
const result = await fetchQuickActions(request, instanceId, currentLanguage || 'de');
|
|
setQuickActions(result.actions || []);
|
|
setQuickActionCategories(result.categories || []);
|
|
} catch {
|
|
setQuickActions([]);
|
|
setQuickActionCategories([]);
|
|
} finally {
|
|
setQuickActionsLoading(false);
|
|
}
|
|
};
|
|
_loadQuickActions();
|
|
}, [instanceId, request, currentLanguage]);
|
|
|
|
const _handleDispatch = useCallback((action: QuickAction) => {
|
|
const targetView = action.config?.targetView;
|
|
if (targetView && mandateId && instanceId) {
|
|
const tab = action.config?.tab;
|
|
const path = `/mandates/${mandateId}/trustee/${instanceId}/${targetView}`;
|
|
navigate(tab ? `${path}?tab=${tab}` : path);
|
|
}
|
|
}, [navigate, mandateId, instanceId]);
|
|
|
|
const isLoading = posLoading || docsLoading || accountingLoading;
|
|
const syncedCount = syncItems.filter(s => s.syncStatus === 'synced').length;
|
|
const syncErrorCount = syncItems.filter(s => s.syncStatus === 'error').length;
|
|
|
|
return (
|
|
<div className={styles.dashboardView}>
|
|
<div className={styles.statsGrid}>
|
|
<div className={styles.statCard}>
|
|
<div className={styles.statIcon}>📊</div>
|
|
<div className={styles.statContent}>
|
|
<div className={styles.statValue}>
|
|
{isLoading ? '...' : positions.length}
|
|
</div>
|
|
<div className={styles.statLabel}>{t('Positionen')}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.statCard}>
|
|
<div className={styles.statIcon}>📄</div>
|
|
<div className={styles.statContent}>
|
|
<div className={styles.statValue}>
|
|
{isLoading ? '...' : documents.length}
|
|
</div>
|
|
<div className={styles.statLabel}>{t('Dokumente')}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.statCard}>
|
|
<div className={styles.statIcon}>
|
|
{accountingConfig?.configured ? '✓' : '○'}
|
|
</div>
|
|
<div className={styles.statContent}>
|
|
<div className={styles.statValueSmall}>
|
|
{isLoading ? '...' : (
|
|
accountingConfig?.configured
|
|
? <>{syncedCount} {t('synchronisiert')}{syncErrorCount > 0 && <span style={{ color: 'var(--error-color, #dc2626)' }}> / {syncErrorCount} {t('Fehler')}</span>}</>
|
|
: t('Nicht konfiguriert')
|
|
)}
|
|
</div>
|
|
<div className={styles.statLabel}>{t('Buchhaltung')}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.statCard}>
|
|
<div className={styles.statIcon}>👤</div>
|
|
<div className={styles.statContent}>
|
|
<div className={styles.statValueSmall}>
|
|
{instance?.userRoles?.length ? (
|
|
instance.userRoles.map((role: string, idx: number) => (
|
|
<div key={idx}>{t(role)}</div>
|
|
))
|
|
) : '-'}
|
|
</div>
|
|
<div className={styles.statLabel}>
|
|
{(instance?.userRoles?.length || 0) === 1 ? t('Deine Rolle') : t('Deine Rollen')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<QuickActionBoard
|
|
actions={quickActions}
|
|
categories={quickActionCategories}
|
|
onDispatch={_handleDispatch}
|
|
loading={quickActionsLoading}
|
|
/>
|
|
|
|
<div className={styles.infoSection}>
|
|
<h3>{t('Instanz-Details')}</h3>
|
|
<div className={styles.infoGrid}>
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.infoLabel}>{t('Instanz:')}</span>
|
|
<span className={styles.infoValue}>{instance?.instanceLabel}</span>
|
|
</div>
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.infoLabel}>{t('Mandant:')}</span>
|
|
<span className={styles.infoValue}>
|
|
{mandate?.label || instance?.mandateLabel || mandate?.name || instance?.mandateName || '-'}
|
|
</span>
|
|
</div>
|
|
{accountingConfig?.configured && (
|
|
<div className={styles.infoItem}>
|
|
<span className={styles.infoLabel}>{t('Buchhaltungssystem:')}</span>
|
|
<span className={styles.infoValue}>
|
|
{accountingConfig.displayLabel || accountingConfig.connectorType}
|
|
{accountingConfig.lastSyncStatus && ` (${t(accountingConfig.lastSyncStatus)})`}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default TrusteeDashboardView;
|