ADD: added admin panel and archiv mandates

This commit is contained in:
hwinkel
2026-03-13 10:58:44 +01:00
parent a742d79457
commit 3a2a94ec19
32 changed files with 2023 additions and 177 deletions
@@ -15,7 +15,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { InvoiceStatusBadge } from "@/components/invoice/invoice-status-badge";
import { formatCurrency, formatDate } from "@/lib/tax";
import { ChevronLeft, Download, CheckCircle, Send, Trash2 } from "lucide-react";
import { ChevronLeft, Download, CheckCircle, Send, Trash2, RotateCcw } from "lucide-react";
import { InvoiceStatus } from "@prisma/client";
export async function loader({
@@ -40,6 +40,7 @@ export async function loader({
if (!invoice) throw new Response("Not Found", { status: 404 });
return {
isAdmin: user.role === "ADMIN",
invoice: {
...invoice,
netTotal: Number(invoice.netTotal),
@@ -63,7 +64,7 @@ export async function loader({
}
export default function InvoiceDetailPage() {
const { invoice } = useLoaderData<typeof loader>();
const { invoice, isAdmin } = useLoaderData<typeof loader>();
const navigate = useNavigate();
const { revalidate } = useRevalidator();
const [loading, setLoading] = useState(false);
@@ -91,8 +92,31 @@ export default function InvoiceDetailPage() {
revalidate();
}
async function handleDelete() {
if (!confirm("Rechnung wirklich löschen?")) return;
async function handleSoftDelete() {
if (!confirm("Rechnung in den Papierkorb verschieben?")) return;
setLoading(true);
await fetch(`/api/invoices/${invoice.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "DELETED" }),
});
setLoading(false);
revalidate();
}
async function handleRestore() {
setLoading(true);
await fetch(`/api/invoices/${invoice.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ status: "CANCELLED" }),
});
setLoading(false);
revalidate();
}
async function handleHardDelete() {
if (!confirm("Rechnung endgültig löschen? Dies kann nicht rückgängig gemacht werden.")) return;
await fetch(`/api/invoices/${invoice.id}`, { method: "DELETE" });
navigate(`/companies/${id}/invoices`);
}
@@ -131,9 +155,11 @@ export default function InvoiceDetailPage() {
{/* Invoice Actions */}
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" onClick={downloadPdf}>
<Download className="h-4 w-4" /> PDF
</Button>
{invoice.status !== "DELETED" && (
<Button variant="outline" size="sm" onClick={downloadPdf}>
<Download className="h-4 w-4" /> PDF
</Button>
)}
{invoice.status === "DRAFT" && (
<Button size="sm" onClick={() => updateStatus(InvoiceStatus.SENT)} disabled={loading}>
<Send className="h-4 w-4" /> Als versendet markieren
@@ -149,16 +175,46 @@ export default function InvoiceDetailPage() {
<CheckCircle className="h-4 w-4" /> Als bezahlt markieren
</Button>
)}
{(invoice.status === "DRAFT" || invoice.status === "CANCELLED") && (
{/* Soft-Delete für alle nicht-gelöschten Status */}
{invoice.status !== "DELETED" && (
<Button
variant="outline"
size="sm"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={handleDelete}
onClick={handleSoftDelete}
disabled={loading}
title="In Papierkorb verschieben"
>
<Trash2 className="h-4 w-4" />
</Button>
)}
{/* Gelöschte Rechnung: Wiederherstellen + endgültig löschen (Admin) */}
{invoice.status === "DELETED" && (
<>
<Button
variant="outline"
size="sm"
className="text-slate-600 hover:text-slate-800"
onClick={handleRestore}
disabled={loading}
title="Als Storniert wiederherstellen"
>
<RotateCcw className="h-4 w-4" /> Wiederherstellen
</Button>
{isAdmin && (
<Button
variant="outline"
size="sm"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={handleHardDelete}
disabled={loading}
title="Endgültig löschen"
>
<Trash2 className="h-4 w-4" /> Endgültig löschen
</Button>
)}
</>
)}
</div>
</div>