152 lines
4.7 KiB
TypeScript
152 lines
4.7 KiB
TypeScript
/**
|
|
* Billing Transactions Page
|
|
*
|
|
* Zeigt die Transaktionshistorie für den Benutzer.
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { useBilling, type BillingTransaction } from '../../hooks/useBilling';
|
|
import { BillingNav } from './BillingNav';
|
|
import styles from './Billing.module.css';
|
|
|
|
import { useLanguage } from '../../providers/language/LanguageContext';
|
|
|
|
// ============================================================================
|
|
// TRANSACTION ROW COMPONENT
|
|
// ============================================================================
|
|
|
|
interface TransactionRowProps {
|
|
transaction: BillingTransaction;
|
|
}
|
|
|
|
const TransactionRow: React.FC<TransactionRowProps> = ({ transaction }) => {
|
|
const formatCurrency = (amount: number) => {
|
|
return new Intl.NumberFormat('de-CH', {
|
|
style: 'currency',
|
|
currency: 'CHF'
|
|
}).format(amount);
|
|
};
|
|
|
|
const formatDate = (dateString?: string) => {
|
|
if (!dateString) return '-';
|
|
return new Date(dateString).toLocaleString('de-CH', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
};
|
|
|
|
const getTypeClass = (type: string) => {
|
|
switch (type) {
|
|
case 'CREDIT': return styles.credit;
|
|
case 'DEBIT': return styles.debit;
|
|
case 'ADJUSTMENT': return styles.adjustment;
|
|
default: return '';
|
|
}
|
|
};
|
|
|
|
const getTypeLabel = (type: string) => {
|
|
switch (type) {
|
|
case 'CREDIT': return 'Gutschrift';
|
|
case 'DEBIT': return 'Belastung';
|
|
case 'ADJUSTMENT': return 'Korrektur';
|
|
default: return type;
|
|
}
|
|
};
|
|
|
|
return (
|
|
<tr>
|
|
<td>{formatDate(transaction.sysCreatedAt)}</td>
|
|
<td>{transaction.mandateName || '-'}</td>
|
|
<td>
|
|
<span className={`${styles.transactionType} ${getTypeClass(transaction.transactionType)}`}>
|
|
{getTypeLabel(transaction.transactionType)}
|
|
</span>
|
|
</td>
|
|
<td>{transaction.description}</td>
|
|
<td>{transaction.aicoreProvider || '-'}</td>
|
|
<td>{transaction.aicoreModel || '-'}</td>
|
|
<td>{transaction.featureCode || '-'}</td>
|
|
<td style={{ textAlign: 'right' }}>
|
|
{transaction.transactionType === 'DEBIT' ? '-' : '+'}{formatCurrency(transaction.amount)}
|
|
</td>
|
|
</tr>
|
|
);
|
|
};
|
|
|
|
// ============================================================================
|
|
// MAIN COMPONENT
|
|
// ============================================================================
|
|
|
|
export const BillingTransactions: React.FC = () => {
|
|
const { t } = useLanguage();
|
|
const { transactions, loading, loadTransactions } = useBilling();
|
|
const [limit, setLimit] = useState(50);
|
|
|
|
useEffect(() => {
|
|
loadTransactions(limit);
|
|
}, [limit, loadTransactions]);
|
|
|
|
const handleLoadMore = () => {
|
|
setLimit(prev => prev + 50);
|
|
};
|
|
|
|
return (
|
|
<div className={styles.billingDashboard}>
|
|
<header className={styles.pageHeader}>
|
|
<h1>{t('Transaktionen')}</h1>
|
|
<p className={styles.subtitle}>{t('Übersicht aller Kontobewegungen')}</p>
|
|
</header>
|
|
|
|
<BillingNav />
|
|
|
|
<section className={styles.section}>
|
|
{loading && transactions.length === 0 ? (
|
|
<div className={styles.loadingPlaceholder}>{t('Transaktionen laden')}</div>
|
|
) : transactions.length === 0 ? (
|
|
<div className={styles.noData}>{t('Keine Transaktionen vorhanden')}</div>
|
|
) : (
|
|
<>
|
|
<div style={{ overflowX: 'auto' }}>
|
|
<table className={styles.transactionsTable}>
|
|
<thead>
|
|
<tr>
|
|
<th>Datum</th>
|
|
<th>{t('Mandant')}</th>
|
|
<th>Typ</th>
|
|
<th>{t('Beschreibung')}</th>
|
|
<th>Anbieter</th>
|
|
<th>Modell</th>
|
|
<th>Feature</th>
|
|
<th style={{ textAlign: 'right' }}>{t('Betrag')}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{transactions.map((transaction) => (
|
|
<TransactionRow key={transaction.id} transaction={transaction} />
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
{transactions.length >= limit && (
|
|
<div style={{ textAlign: 'center', marginTop: 'var(--spacing-md)' }}>
|
|
<button
|
|
className={`${styles.button} ${styles.buttonSecondary}`}
|
|
onClick={handleLoadMore}
|
|
disabled={loading}
|
|
>
|
|
{loading ? t('Laden') : t('Mehr laden')}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</>
|
|
)}
|
|
</section>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default BillingTransactions;
|