Files
webshop/shop/app/admin/companies/page.tsx
DanielS 7a14aa0f75
All checks were successful
Staging Build / build (push) Successful in 2m35s
feat: add company management and user-company assignment in admin panel
2026-06-24 17:12:40 +02:00

187 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { useState, useEffect } from 'react'
import { Building2, Plus, Trash2, Loader2 } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { getCompanies, createCompany, deleteCompany } from '@/lib/actions/companies'
export default function CompaniesPage() {
const [companies, setCompanies] = useState<any[]>([])
const [loading, setLoading] = useState(true)
const [isCreateOpen, setIsCreateOpen] = useState(false)
const [name, setName] = useState('')
const [creating, setCreating] = useState(false)
const [createError, setCreateError] = useState<string | null>(null)
const [deletingId, setDeletingId] = useState<string | null>(null)
const loadCompanies = async () => {
setLoading(true)
try {
const data = await getCompanies()
setCompanies(data)
} catch (err) {
console.error('Fehler beim Laden der Firmen:', err)
} finally {
setLoading(false)
}
}
useEffect(() => {
loadCompanies()
}, [])
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault()
setCreating(true)
setCreateError(null)
try {
await createCompany(name)
setIsCreateOpen(false)
setName('')
loadCompanies()
} catch (err: any) {
setCreateError(err.message || 'Fehler beim Anlegen.')
} finally {
setCreating(false)
}
}
const handleDelete = async (id: string, firmName: string) => {
if (!confirm(`Firma "${firmName}" wirklich löschen? Alle zugewiesenen Partner werden getrennt.`)) return
setDeletingId(id)
try {
await deleteCompany(id)
loadCompanies()
} catch (err) {
console.error('Fehler beim Löschen:', err)
} finally {
setDeletingId(null)
}
}
return (
<div className="p-6 max-w-4xl mx-auto text-slate-900 dark:text-white space-y-8">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-extrabold tracking-tight flex items-center gap-3">
<Building2 className="w-8 h-8 text-blue-500" />
Firmenverwaltung
</h1>
<p className="text-slate-500 dark:text-slate-400 text-sm mt-1">
Verwalten Sie Firmen und weisen Sie Partner zu.
</p>
</div>
<Button
onClick={() => setIsCreateOpen(true)}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold"
>
<Plus className="w-4 h-4 mr-2" /> Firma anlegen
</Button>
</div>
{/* Firmen-Tabelle */}
<div className="p-5 bg-white dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-white/10 shadow-sm">
{loading ? (
<div className="flex justify-center items-center py-16">
<Loader2 className="w-8 h-8 text-blue-500 animate-spin" />
<span className="ml-3 text-slate-400 text-sm">Lade Firmen...</span>
</div>
) : companies.length === 0 ? (
<div className="text-center py-16 border-2 border-dashed border-slate-200 dark:border-white/5 rounded-xl text-slate-500">
<Building2 className="w-10 h-10 mx-auto mb-3 opacity-30" />
Noch keine Firmen angelegt.
</div>
) : (
<table className="w-full text-left">
<thead>
<tr className="border-b border-slate-200 dark:border-white/10 text-slate-400 text-xs font-semibold uppercase">
<th className="pb-3">Firmenname</th>
<th className="pb-3">Erstellt am</th>
<th className="pb-3 text-right">Aktionen</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-100 dark:divide-white/5">
{companies.map((company) => (
<tr key={company.id} className="hover:bg-slate-50 dark:hover:bg-white/3 transition-colors">
<td className="py-4 font-semibold text-slate-900 dark:text-white">
<div className="flex items-center gap-2">
<Building2 className="w-4 h-4 text-blue-500 shrink-0" />
{company.name}
</div>
</td>
<td className="py-4 text-sm text-slate-500 dark:text-slate-400">
{company.created_at ? new Date(company.created_at).toLocaleDateString('de-DE') : ''}
</td>
<td className="py-4 text-right">
<Button
variant="ghost"
size="icon"
disabled={deletingId === company.id}
onClick={() => handleDelete(company.id, company.name)}
className="h-8 w-8 text-red-500 hover:text-red-400 hover:bg-red-500/10"
title="Firma löschen"
>
{deletingId === company.id
? <Loader2 className="h-4 w-4 animate-spin" />
: <Trash2 className="h-4 w-4" />}
</Button>
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
{/* Dialog: Neue Firma */}
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
<DialogContent className="bg-white dark:bg-slate-950 border-slate-200 dark:border-white/10 text-slate-900 dark:text-white shadow-2xl">
<DialogHeader>
<DialogTitle>Neue Firma anlegen</DialogTitle>
<DialogDescription className="text-slate-500 dark:text-slate-400">
Geben Sie den Namen der Firma ein. Partner können danach zugewiesen werden.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleCreate} className="space-y-4 mt-2">
<div className="space-y-2">
<Label htmlFor="company-name">Firmenname</Label>
<Input
id="company-name"
type="text"
required
placeholder="Muster GmbH"
value={name}
onChange={(e) => setName(e.target.value)}
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"
/>
</div>
{createError && (
<p className="text-sm text-red-600 dark:text-red-400">{createError}</p>
)}
<DialogFooter className="pt-2">
<Button type="button" variant="outline" onClick={() => setIsCreateOpen(false)} disabled={creating}>
Abbrechen
</Button>
<Button type="submit" disabled={creating} className="bg-blue-600 hover:bg-blue-700 text-white font-semibold">
{creating ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : null}
Anlegen
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
)
}