Compare commits
2 Commits
fc51d53aa3
...
10f8683d7f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10f8683d7f | ||
|
|
19e9733144 |
53
shop/app/api/admin/orders/[id]/download/route.ts
Normal file
53
shop/app/api/admin/orders/[id]/download/route.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { createAdminClient } from "@/lib/supabase/admin";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
request: NextRequest,
|
||||||
|
{ params }: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { id } = await params;
|
||||||
|
const adminDb = createAdminClient();
|
||||||
|
|
||||||
|
// 1. Get the order from database
|
||||||
|
const { data: order, error } = await adminDb
|
||||||
|
.from("orders")
|
||||||
|
.select("pdf_url, order_number")
|
||||||
|
.eq("id", id)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error || !order || !order.pdf_url) {
|
||||||
|
return new NextResponse("PDF not found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Determine file name in storage bucket
|
||||||
|
const storageFileName = `ab_${id}.pdf`;
|
||||||
|
|
||||||
|
// 3. Download file from Supabase storage
|
||||||
|
const { data: fileData, error: downloadError } = await adminDb
|
||||||
|
.storage
|
||||||
|
.from("invoices")
|
||||||
|
.download(storageFileName);
|
||||||
|
|
||||||
|
if (downloadError || !fileData) {
|
||||||
|
console.error("Failed to download PDF from storage:", downloadError);
|
||||||
|
return new NextResponse("Error downloading file from storage", { status: 500 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Return as attachment
|
||||||
|
const buffer = await fileData.arrayBuffer();
|
||||||
|
const downloadFileName = `Anfragebestaetigung_${order.order_number || id.slice(0, 8)}.pdf`;
|
||||||
|
|
||||||
|
const headers = new Headers();
|
||||||
|
headers.append("Content-Disposition", `attachment; filename="${downloadFileName}"`);
|
||||||
|
headers.append("Content-Type", "application/pdf");
|
||||||
|
|
||||||
|
return new NextResponse(buffer, {
|
||||||
|
status: 200,
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Download endpoint failed:", err);
|
||||||
|
return new NextResponse(err.message || "Internal Server Error", { status: 500 });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -224,7 +224,7 @@ export function OrdersTable({ initialOrders }: OrdersTableProps) {
|
|||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="secondary" size="sm" asChild className="h-8 text-xs">
|
<Button variant="secondary" size="sm" asChild className="h-8 text-xs">
|
||||||
<a href={order.pdf_url} download>
|
<a href={`/api/admin/orders/${order.id}/download`}>
|
||||||
<Download className="w-3.5 h-3.5" />
|
<Download className="w-3.5 h-3.5" />
|
||||||
</a>
|
</a>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -286,8 +286,7 @@ export function UserList({ initialUsers, companies }: UserListProps) {
|
|||||||
<select
|
<select
|
||||||
value={role}
|
value={role}
|
||||||
onChange={(e) => handleRoleChange(user.id, e.target.value as Role)}
|
onChange={(e) => handleRoleChange(user.id, e.target.value as Role)}
|
||||||
className={`text-xs font-semibold rounded px-3 py-1.5 cursor-pointer focus:outline-none focus:ring-2 transition-colors bg-white dark:bg-slate-950/80 ${
|
className={`text-xs font-semibold rounded px-3 py-1.5 cursor-pointer focus:outline-none focus:ring-2 transition-colors bg-white dark:bg-slate-950/80 ${role === 'admin'
|
||||||
role === 'admin'
|
|
||||||
? 'text-purple-600 dark:text-purple-300 border border-purple-500/40 focus:ring-purple-500/50'
|
? 'text-purple-600 dark:text-purple-300 border border-purple-500/40 focus:ring-purple-500/50'
|
||||||
: role === 'gesperrt'
|
: role === 'gesperrt'
|
||||||
? 'text-red-600 dark:text-red-300 border border-red-500/40 focus:ring-red-500/50'
|
? 'text-red-600 dark:text-red-300 border border-red-500/40 focus:ring-red-500/50'
|
||||||
@@ -322,43 +321,41 @@ export function UserList({ initialUsers, companies }: UserListProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{companyDropdownOpen === user.id && (
|
{companyDropdownOpen === user.id && (
|
||||||
<div className="absolute z-50 mt-1 right-0 w-56 bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10 rounded-lg shadow-xl overflow-hidden">
|
<div className="absolute z-50 left-0 right-0 mt-1 bg-white dark:bg-slate-900 border border-slate-200 dark:border-white/10 rounded-md shadow-lg p-1 flex flex-col max-h-60 overflow-y-auto">
|
||||||
<div className="p-2 border-b border-slate-200 dark:border-white/10">
|
<div className="flex items-center gap-2 px-2 py-1.5 border-b border-slate-100 dark:border-white/5 mb-1 sticky top-0 bg-white dark:bg-slate-900 z-10">
|
||||||
|
<Search className="h-3.5 w-3.5 text-slate-500 shrink-0" />
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Firma suchen..."
|
placeholder="Firma suchen..."
|
||||||
value={companySearch}
|
value={companySearch}
|
||||||
onChange={(e) => setCompanySearch(e.target.value)}
|
onChange={(e) => setCompanySearch(e.target.value)}
|
||||||
className="w-full text-xs bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded px-2 py-1.5 text-slate-900 dark:text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-1 focus:ring-primary/50"
|
className="w-full text-xs bg-transparent border-0 p-0 focus:outline-none focus:ring-0 text-slate-900 dark:text-slate-200 placeholder-slate-500"
|
||||||
autoFocus
|
onClick={(e) => e.stopPropagation()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-h-40 overflow-y-auto">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
onClick={() => handleCompanyChange(user.id, '')}
|
||||||
onClick={() => { handleCompanyChange(user.id, ''); setCompanyDropdownOpen(null); setCompanySearch(''); }}
|
className="w-full text-left px-2 py-1.5 text-xs rounded hover:bg-slate-100 dark:hover:bg-white/5 text-red-500 font-medium"
|
||||||
className={`w-full text-left text-xs px-3 py-2 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors ${
|
|
||||||
!user.company_id ? 'text-primary font-semibold' : 'text-slate-700 dark:text-slate-300'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Keine Firma
|
Keine Firma (Zuweisung aufheben)
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{companies
|
{companies
|
||||||
.filter((c) => c.name.toLowerCase().includes(companySearch.toLowerCase()))
|
.filter((c) => c.name.toLowerCase().includes(companySearch.toLowerCase()))
|
||||||
.map((c) => (
|
.map((c) => (
|
||||||
<button
|
<button
|
||||||
key={c.id}
|
key={c.id}
|
||||||
type="button"
|
onClick={() => handleCompanyChange(user.id, c.id)}
|
||||||
onClick={() => { handleCompanyChange(user.id, c.id); setCompanyDropdownOpen(null); setCompanySearch(''); }}
|
className={`w-full text-left px-2 py-1.5 text-xs rounded hover:bg-slate-100 dark:hover:bg-white/5 transition-colors ${user.company_id === c.id
|
||||||
className={`w-full text-left text-xs px-3 py-2 hover:bg-slate-100 dark:hover:bg-white/10 transition-colors ${
|
? 'bg-primary/10 text-primary font-medium'
|
||||||
user.company_id === c.id ? 'text-primary font-semibold' : 'text-slate-700 dark:text-slate-300'
|
: 'text-slate-700 dark:text-slate-300'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{c.name}
|
{c.name}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
|
|||||||
Reference in New Issue
Block a user