import { requireUser } from "@/session.server"; import prisma from "@/lib/prisma.server"; import { ChevronLeft, Loader2, Plus, Pencil, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent } from "@/components/ui/card"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Badge } from "@/components/ui/badge"; import { useState } from "react"; import { formatCurrency } from "@/lib/tax"; import { useLoaderData, Link, useRevalidator } from "react-router"; type Transaction = { id: string; date: string; account: 'kasse' | 'bank'; type: 'einlage' | 'entnahme'; amount: number; description: string; isBusinessRecord: boolean; kategorie: string | null; }; export async function loader({ request, params }: { request: Request; params: { id: string } }) { const user = await requireUser(request); const company = await prisma.company.findFirst({ where: { id: params.id, userId: user.id }, select: { id: true, name: true }, }); if (!company) throw new Response("Company not Found", { status: 404 }); const buchungen = await prisma.buchung.findMany({ where: { companyId: company.id }, orderBy: { date: 'desc' }, select: { id: true, date: true, account: true, type: true, amount: true, description: true, isBusinessRecord: true, kategorie: true, }, }); const transactions: Transaction[] = buchungen.map((b): Transaction => ({ id: b.id, date: b.date.toISOString().split('T')[0], account: b.account === 'BANK' ? 'bank' : 'kasse', type: b.type === 'EINLAGE' ? 'einlage' : 'entnahme', amount: Number(b.amount), description: b.description || '', isBusinessRecord: b.isBusinessRecord, kategorie: b.kategorie || null, })); const balance = transactions.reduce((sum: number, t: Transaction) => sum + (t.type === 'einlage' ? t.amount : -t.amount), 0); return { companyId: company.id, companyName: company.name, transactions, balance, }; } export default function CompanyMoney() { const { transactions: initialTransactions, companyId, companyName, balance } = useLoaderData(); const { revalidate } = useRevalidator(); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [editingTransaction, setEditingTransaction] = useState(null); const [isUmbuchung, setIsUmbuchung] = useState(false); const [form, setForm] = useState({ date: new Date().toISOString().split('T')[0], account: 'kasse' as 'kasse' | 'bank', type: 'einlage' as 'einlage' | 'entnahme', amount: '', description: '', toAccount: 'bank' as 'kasse' | 'bank', }); function openCreate() { setEditingTransaction(null); setIsUmbuchung(false); setForm({ date: new Date().toISOString().split('T')[0], account: 'kasse', type: 'einlage', amount: '', description: '', toAccount: 'bank', }); setDialogOpen(true); } function openCreateUmbuchung() { setEditingTransaction(null); setIsUmbuchung(true); setForm({ date: new Date().toISOString().split('T')[0], account: 'kasse', type: 'umbuchung', amount: '', description: '', toAccount: 'bank', }); setDialogOpen(true); } function openEdit(transaction: Transaction) { setEditingTransaction(transaction); setForm({ date: transaction.date, account: transaction.account, type: transaction.type, amount: String(transaction.amount), description: transaction.description, }); setDialogOpen(true); } async function handleSave() { setSaving(true); const payload = isUmbuchung ? { date: form.date, account: form.account, type: 'umbuchung', toAccount: form.toAccount, amount: parseFloat(form.amount), description: form.description, } : { date: form.date, account: form.account, type: form.type, amount: parseFloat(form.amount), description: form.description, }; try { if (editingTransaction) { const res = await fetch(`/api/companies/${companyId}/money?transactionId=${editingTransaction.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { const error = await res.json(); alert(error.error || "Fehler beim Speichern"); return; } } else { const res = await fetch(`/api/companies/${companyId}/money`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }); if (!res.ok) { const error = await res.json(); alert(error.error || "Fehler beim Speichern"); return; } } setDialogOpen(false); revalidate(); } finally { setSaving(false); } } async function handleDelete(id: string) { if (!confirm("Transaktion wirklich löschen?")) return; setDeleting(id); await fetch(`/api/companies/${companyId}/money?transactionId=${id}`, { method: "DELETE" }); setDeleting(null); revalidate(); } const sortedTransactions = [...initialTransactions].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); const kasseBalance = initialTransactions .filter((t) => t.account === 'kasse') .reduce((sum, t) => sum + (t.type === 'einlage' ? t.amount : -t.amount), 0); const bankBalance = initialTransactions .filter((t) => t.account === 'bank') .reduce((sum, t) => sum + (t.type === 'einlage' ? t.amount : -t.amount), 0); const formValid = form.date && form.amount && parseFloat(form.amount) > 0; return (
Zurück zum Mandanten

Kasse und Bank

{companyName}

{/* Zusammenfassung */}

Kasse (Saldo)

{formatCurrency(kasseBalance)}

Bank (Saldo)

{formatCurrency(bankBalance)}

Gesamter Kontostand

{formatCurrency(balance)}

{/* Split-View: Kasse und Bank nebeneinander */} {sortedTransactions.length === 0 ? (

Noch keine Transaktionen erfasst.

) : (
{/* Kasse Tabelle */}

Kasse

{sortedTransactions .filter((t) => t.account === 'kasse') .map((transaction) => ( ))}
Datum Typ Beschreibung Kategorie Betrag
{transaction.date} {transaction.type === 'einlage' ? 'Einlage' : 'Entnahme'} {transaction.description} {transaction.kategorie || '—'} {transaction.type === 'einlage' ? '+' : '-'}{formatCurrency(transaction.amount)} {transaction.isBusinessRecord ? ( Automatisch ) : (
)}
{/* Bank Tabelle */}

Bank

{sortedTransactions .filter((t) => t.account === 'bank') .map((transaction) => ( ))}
Datum Typ Beschreibung Kategorie Betrag
{transaction.date} {transaction.type === 'einlage' ? 'Einlage' : 'Entnahme'} {transaction.description} {transaction.kategorie || '—'} {transaction.type === 'einlage' ? '+' : '-'}{formatCurrency(transaction.amount)} {transaction.isBusinessRecord ? ( Automatisch ) : (
)}
)} {/* Dialog: Anlegen / Bearbeiten */} {editingTransaction ? "Transaktion bearbeiten" : "Neue Transaktion"}
setForm((f) => ({ ...f, date: e.target.value }))} className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
{isUmbuchung ? ( <>
{form.toAccount === 'kasse' ? 'Kasse' : 'Bank'}
) : ( <>
)}
setForm((f) => ({ ...f, amount: e.target.value }))} placeholder="0,00" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
setForm((f) => ({ ...f, description: e.target.value }))} placeholder="z.B. Barentnahme, Gehalt" className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500" />
); }