feat: add delivery address migration, update companies server actions and rewrite companies page to support creation and edit of full address
All checks were successful
Staging Build / build (push) Successful in 2m37s

This commit is contained in:
DanielS
2026-06-25 01:52:05 +02:00
parent 85df922498
commit a1ad75c2db
4 changed files with 168 additions and 37 deletions

View File

@@ -1,7 +1,7 @@
'use client'
import { useState, useEffect } from 'react'
import { Building2, Plus, Trash2, Loader2 } from 'lucide-react'
import { Building2, Plus, Trash2, Loader2, Pencil, Mail, MapPin } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
@@ -13,13 +13,20 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog'
import { getCompanies, createCompany, deleteCompany } from '@/lib/actions/companies'
import { getCompanies, createCompany, updateCompany, 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 [isOpen, setIsOpen] = useState(false)
const [editingCompany, setEditingCompany] = useState<any>(null)
const [name, setName] = useState('')
const [street, setStreet] = useState('')
const [zip, setZip] = useState('')
const [city, setCity] = useState('')
const [email, setEmail] = useState('')
const [creating, setCreating] = useState(false)
const [createError, setCreateError] = useState<string | null>(null)
const [deletingId, setDeletingId] = useState<string | null>(null)
@@ -40,17 +47,38 @@ export default function CompaniesPage() {
loadCompanies()
}, [])
const handleCreate = async (e: React.FormEvent) => {
const handleOpenDialog = (company: any = null) => {
setEditingCompany(company)
setName(company ? company.name || '' : '')
setStreet(company ? company.street || '' : '')
setZip(company ? company.zip || '' : '')
setCity(company ? company.city || '' : '')
setEmail(company ? company.email || '' : '')
setCreateError(null)
setIsOpen(true)
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setCreating(true)
setCreateError(null)
try {
await createCompany(name)
setIsCreateOpen(false)
setName('')
const payload = {
name: name.trim(),
street: street.trim(),
zip: zip.trim(),
city: city.trim(),
email: email.trim(),
}
if (editingCompany) {
await updateCompany(editingCompany.id, payload)
} else {
await createCompany(payload)
}
setIsOpen(false)
loadCompanies()
} catch (err: any) {
setCreateError(err.message || 'Fehler beim Anlegen.')
setCreateError(err.message || 'Fehler beim Speichern.')
} finally {
setCreating(false)
}
@@ -70,7 +98,7 @@ export default function CompaniesPage() {
}
return (
<div className="p-6 max-w-4xl mx-auto text-slate-900 dark:text-white space-y-8">
<div className="p-6 max-w-5xl mx-auto text-slate-900 dark:text-white space-y-8">
{/* Header */}
<div className="flex items-center justify-between">
<div>
@@ -79,11 +107,11 @@ export default function CompaniesPage() {
Firmenverwaltung
</h1>
<p className="text-slate-500 dark:text-slate-400 text-sm mt-1">
Verwalten Sie Firmen und weisen Sie Partner zu.
Verwalten Sie Firmen, deren Lieferadresse und weisen Sie Partner zu.
</p>
</div>
<Button
onClick={() => setIsCreateOpen(true)}
onClick={() => handleOpenDialog()}
className="bg-blue-600 hover:bg-blue-700 text-white font-semibold"
>
<Plus className="w-4 h-4 mr-2" /> Firma anlegen
@@ -107,6 +135,8 @@ export default function CompaniesPage() {
<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">Lieferadresse</th>
<th className="pb-3">E-Mail</th>
<th className="pb-3">Erstellt am</th>
<th className="pb-3 text-right">Aktionen</th>
</tr>
@@ -120,22 +150,49 @@ export default function CompaniesPage() {
{company.name}
</div>
</td>
<td className="py-4 text-sm text-slate-700 dark:text-slate-300">
<div className="flex items-center gap-2">
<MapPin className="w-3.5 h-3.5 text-slate-400 shrink-0" />
<span>
{company.street ? `${company.street}, ${company.zip} ${company.city}` : ''}
</span>
</div>
</td>
<td className="py-4 text-sm text-slate-700 dark:text-slate-300">
{company.email ? (
<div className="flex items-center gap-2">
<Mail className="w-3.5 h-3.5 text-slate-400 shrink-0" />
<span>{company.email}</span>
</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>
<div className="flex justify-end gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => handleOpenDialog(company)}
className="h-8 w-8 text-blue-500 hover:text-blue-400 hover:bg-blue-500/10"
title="Firma bearbeiten"
>
<Pencil className="h-4 w-4" />
</Button>
<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>
</div>
</td>
</tr>
))}
@@ -144,16 +201,18 @@ export default function CompaniesPage() {
)}
</div>
{/* Dialog: Neue Firma */}
<Dialog open={isCreateOpen} onOpenChange={setIsCreateOpen}>
{/* Dialog: Firma anlegen / bearbeiten */}
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<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>
<DialogTitle>
{editingCompany ? 'Firma bearbeiten' : '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.
Geben Sie die Firmendetails und die vollständige Lieferadresse ein. Alle Felder sind Pflichtfelder.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleCreate} className="space-y-4 mt-2">
<form onSubmit={handleSubmit} className="space-y-4 mt-2">
<div className="space-y-2">
<Label htmlFor="company-name">Firmenname</Label>
<Input
@@ -166,16 +225,70 @@ export default function CompaniesPage() {
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"
/>
</div>
<div className="space-y-2">
<Label htmlFor="company-street">Straße & Hausnummer</Label>
<Input
id="company-street"
type="text"
required
placeholder="Musterstraße 123"
value={street}
onChange={(e) => setStreet(e.target.value)}
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"
/>
</div>
<div className="grid grid-cols-3 gap-4">
<div className="space-y-2 col-span-1">
<Label htmlFor="company-zip">PLZ</Label>
<Input
id="company-zip"
type="text"
required
placeholder="12345"
value={zip}
onChange={(e) => setZip(e.target.value)}
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"
/>
</div>
<div className="space-y-2 col-span-2">
<Label htmlFor="company-city">Ort</Label>
<Input
id="company-city"
type="text"
required
placeholder="Musterstadt"
value={city}
onChange={(e) => setCity(e.target.value)}
className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="company-email">E-Mail für Lieferadresse</Label>
<Input
id="company-email"
type="email"
required
placeholder="lieferung@musterfirma.de"
value={email}
onChange={(e) => setEmail(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}>
<Button type="button" variant="outline" onClick={() => setIsOpen(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
{editingCompany ? 'Speichern' : 'Anlegen'}
</Button>
</DialogFooter>
</form>