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
All checks were successful
Staging Build / build (push) Successful in 2m37s
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user