fix: resolve hydration and blocking route issues and add admin order archive
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1 +1,4 @@
|
|||||||
example_data/
|
example_data/
|
||||||
|
shop/Leitfaden
|
||||||
|
.gitignore
|
||||||
|
db-access.txt
|
||||||
|
|||||||
108
shop/app/admin/orders/page.tsx
Normal file
108
shop/app/admin/orders/page.tsx
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
import { createClient } from '@/lib/supabase/server'
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||||
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||||
|
import { Badge } from '@/components/ui/badge'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { FileText, Download, ExternalLink } from 'lucide-react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { Suspense } from 'react'
|
||||||
|
|
||||||
|
export default async function AdminOrdersPage() {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold tracking-tight">Bestellungen & Rechnungen</h1>
|
||||||
|
<p className="text-muted-foreground">Verwalten Sie alle Kundenbestellungen und greifen Sie auf generierte PDFs zu.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Suspense fallback={<div className="h-40 w-full animate-pulse bg-white/5 rounded-xl" />}>
|
||||||
|
<OrdersData />
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function OrdersData() {
|
||||||
|
const supabase = await createClient()
|
||||||
|
|
||||||
|
const { data: orders, error } = await supabase
|
||||||
|
.from('orders')
|
||||||
|
.select('*')
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <div>Fehler beim Laden der Bestellungen: {error.message}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="glass-dark border-white/10">
|
||||||
|
<CardContent className="p-0">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow className="border-white/10 hover:bg-transparent">
|
||||||
|
<TableHead className="w-[100px]">ID</TableHead>
|
||||||
|
<TableHead>Datum</TableHead>
|
||||||
|
<TableHead>Kunde</TableHead>
|
||||||
|
<TableHead>Betrag</TableHead>
|
||||||
|
<TableHead>Status</TableHead>
|
||||||
|
<TableHead className="text-right">Rechnung</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{orders?.length === 0 ? (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={6} className="text-center py-12 text-muted-foreground">
|
||||||
|
Noch keine Bestellungen vorhanden.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
) : (
|
||||||
|
orders?.map((order) => (
|
||||||
|
<TableRow key={order.id} className="border-white/5 hover:bg-white/5">
|
||||||
|
<TableCell className="font-mono text-xs text-muted-foreground">
|
||||||
|
{order.id.slice(0, 8)}...
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{new Date(order.created_at).toLocaleDateString('de-DE')}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<div className="font-medium">{order.customer_data?.company_name || 'Privatkunde'}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">{order.customer_data?.first_name} {order.customer_data?.last_name}</div>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="font-semibold">
|
||||||
|
{new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(order.total_price)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Badge variant={order.status === 'completed' ? 'default' : 'secondary'} className="capitalize">
|
||||||
|
{order.status}
|
||||||
|
</Badge>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{order.pdf_url ? (
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button variant="outline" size="sm" asChild className="h-8 border-white/10 hover:bg-primary/20">
|
||||||
|
<a href={order.pdf_url} target="_blank" rel="noopener noreferrer">
|
||||||
|
<ExternalLink className="w-4 h-4 mr-2" /> Ansehen
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
<Button variant="secondary" size="sm" asChild className="h-8">
|
||||||
|
<a href={order.pdf_url} download>
|
||||||
|
<Download className="w-4 h-4" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground italic">Kein PDF</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getProducts } from '@/lib/actions/products'
|
import { getProducts, getCategories } from '@/lib/actions/products'
|
||||||
|
|
||||||
import { ProductList } from '@/components/admin/product-list'
|
import { ProductList } from '@/components/admin/product-list'
|
||||||
import { CreateProductDialog } from '@/components/admin/create-product-dialog'
|
import { CreateProductDialog } from '@/components/admin/create-product-dialog'
|
||||||
@@ -19,11 +19,9 @@ export default async function AdminProductsPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<CreateProductDialog>
|
<Suspense fallback={<div className="h-10 w-32 animate-pulse bg-white/5 rounded-md" />}>
|
||||||
<button className="inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50">
|
<CreateProductAction />
|
||||||
<Plus className="mr-2 h-4 w-4" /> Produkt hinzufügen
|
</Suspense>
|
||||||
</button>
|
|
||||||
</CreateProductDialog>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Suspense fallback={<div className="h-40 w-full animate-pulse bg-white/5 rounded-xl" />}>
|
<Suspense fallback={<div className="h-40 w-full animate-pulse bg-white/5 rounded-xl" />}>
|
||||||
@@ -35,5 +33,13 @@ export default async function AdminProductsPage() {
|
|||||||
|
|
||||||
async function ProductDataWrapper() {
|
async function ProductDataWrapper() {
|
||||||
const products = await getProducts()
|
const products = await getProducts()
|
||||||
return <ProductList initialProducts={products} />
|
const categories = await getCategories()
|
||||||
|
return <ProductList initialProducts={products} categories={categories} />
|
||||||
|
}
|
||||||
|
|
||||||
|
async function CreateProductAction() {
|
||||||
|
const categories = await getCategories()
|
||||||
|
return (
|
||||||
|
<CreateProductDialog categories={categories} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const defaultUrl = process.env.VERCEL_URL
|
|||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
metadataBase: new URL(defaultUrl),
|
metadataBase: new URL(defaultUrl),
|
||||||
title: "CloudShop | Modulare Software-Lösungen",
|
title: "CASPOSShop | Modulare Software-Lösungen",
|
||||||
description: "Konfigurieren und bestellen Sie Ihre maßgeschneiderte Business-Software in Minuten.",
|
description: "Konfigurieren und bestellen Sie Ihre maßgeschneiderte Business-Software in Minuten.",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user