Compare commits

...

2 Commits

Author SHA1 Message Date
DanielS
b537ca50cf feat: Add email field to end_customers table and UI screens
All checks were successful
Staging Build / build (push) Successful in 2m36s
2026-06-25 02:29:33 +02:00
DanielS
b9e6559bff style: Update warning title for unassigned users 2026-06-25 02:27:26 +02:00
7 changed files with 54 additions and 19 deletions

View File

@@ -3,19 +3,19 @@
import { useState, useEffect } from 'react'
import { useParams, useRouter } from 'next/navigation'
import Link from 'next/link'
import {
Building2,
Plus,
Trash2,
Loader2,
Pencil,
ArrowLeft,
Search,
MapPin,
User,
import {
Building2,
Plus,
Trash2,
Loader2,
Pencil,
ArrowLeft,
Search,
MapPin,
User,
Users,
Briefcase,
AlertCircle
Briefcase,
AlertCircle
} from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@@ -52,7 +52,7 @@ export default function CompanyCustomersPage() {
// Dialog State
const [isOpen, setIsOpen] = useState(false)
const [editingCustomer, setEditingCustomer] = useState<any>(null)
// Form State
const [companyName, setCompanyName] = useState('')
const [firstName, setFirstName] = useState('')
@@ -62,6 +62,7 @@ export default function CompanyCustomersPage() {
const [city, setCity] = useState('')
const [vatId, setVatId] = useState('')
const [partnerId, setPartnerId] = useState('')
const [email, setEmail] = useState('')
const [error, setError] = useState<string | null>(null)
const loadData = async () => {
@@ -98,6 +99,7 @@ export default function CompanyCustomersPage() {
setCity(customer ? customer.city || '' : '')
setVatId(customer ? customer.vat_id || '' : '')
setPartnerId(customer ? customer.partner_id || '' : (partners.length > 0 ? partners[0].id : ''))
setEmail(customer ? customer.email || '' : '')
setError(null)
setIsOpen(true)
}
@@ -120,6 +122,7 @@ export default function CompanyCustomersPage() {
zip: zip.trim() || undefined,
city: city.trim() || undefined,
vat_id: vatId.trim() || undefined,
email: email.trim() || undefined,
}
if (editingCustomer) {
@@ -155,7 +158,8 @@ export default function CompanyCustomersPage() {
const personMatch = `${c.first_name || ''} ${c.last_name || ''}`.toLowerCase().includes(q)
const addressMatch = `${c.street || ''} ${c.zip || ''} ${c.city || ''}`.toLowerCase().includes(q)
const partnerMatch = c.partner_name?.toLowerCase().includes(q)
return nameMatch || personMatch || addressMatch || partnerMatch
const emailMatch = c.email?.toLowerCase().includes(q)
return nameMatch || personMatch || addressMatch || partnerMatch || emailMatch
})
@@ -164,8 +168,8 @@ export default function CompanyCustomersPage() {
<div className="p-6 max-w-5xl mx-auto text-slate-900 dark:text-white space-y-8">
{/* Back Link */}
<div>
<Link
href="/admin/companies"
<Link
href="/admin/companies"
className="inline-flex items-center gap-2 text-sm font-semibold text-slate-500 hover:text-slate-900 dark:text-slate-400 dark:hover:text-white transition-colors"
>
<ArrowLeft className="w-4 h-4" />
@@ -199,7 +203,7 @@ export default function CompanyCustomersPage() {
<div className="p-4 bg-amber-50 dark:bg-amber-950/20 border border-amber-200 dark:border-amber-900/30 rounded-xl flex gap-3 text-amber-800 dark:text-amber-300 text-sm">
<AlertCircle className="w-5 h-5 shrink-0 text-amber-600 dark:text-amber-400" />
<div>
<p className="font-semibold">Keine Partner vorhanden</p>
<p className="font-semibold">Keine Benutzer zugewiesen</p>
<p className="mt-1">
Diese Firma hat aktuell keine zugewiesenen Partner (Benutzer mit der Rolle &quot;partner&quot;).
Sie müssen zuerst in der <Link href="/admin/users" className="underline font-semibold hover:text-amber-900 dark:hover:text-amber-200">Benutzerverwaltung</Link> Partner erstellen und dieser Firma zuweisen, um Endkunden anzulegen.
@@ -237,6 +241,7 @@ export default function CompanyCustomersPage() {
<tr className="border-b border-slate-200 dark:border-white/10 text-slate-400 text-xs font-semibold uppercase">
<th className="pb-3">Endkunde (Firma / Kontakt)</th>
<th className="pb-3">Lieferadresse</th>
<th className="pb-3">E-Mail</th>
<th className="pb-3">USt-ID</th>
<th className="pb-3">Zugeordneter Partner</th>
<th className="pb-3 text-right">Aktionen</th>
@@ -268,6 +273,9 @@ export default function CompanyCustomersPage() {
<span className="text-slate-400 dark:text-slate-600"></span>
)}
</td>
<td className="py-4 text-sm text-slate-700 dark:text-slate-300">
{customer.email || <span className="text-slate-400 dark:text-slate-600"></span>}
</td>
<td className="py-4 text-sm text-slate-700 dark:text-slate-300">
{customer.vat_id || <span className="text-slate-400 dark:text-slate-600"></span>}
</td>
@@ -425,6 +433,18 @@ export default function CompanyCustomersPage() {
</div>
</div>
<div className="space-y-2">
<Label htmlFor="cust-email">E-Mail</Label>
<Input
id="cust-email"
type="email"
placeholder="kunde@beispiel.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>
{error && (
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
)}

View File

@@ -21,7 +21,7 @@ export default function CustomerDetailPage({ params }: { params: Promise<{ id: s
const [anonymizing, setAnonymizing] = useState(false)
const [showGdprConfirm, setShowGdprConfirm] = useState(false)
const [form, setForm] = useState({
company_name: '', vat_id: '', first_name: '', last_name: '', street: '', zip: '', city: '',
company_name: '', vat_id: '', first_name: '', last_name: '', street: '', zip: '', city: '', email: '',
})
useEffect(() => {
@@ -37,6 +37,7 @@ export default function CustomerDetailPage({ params }: { params: Promise<{ id: s
street: c.street ?? '',
zip: c.zip ?? '',
city: c.city ?? '',
email: c.email ?? '',
})
setLoading(false)
})
@@ -121,6 +122,7 @@ export default function CustomerDetailPage({ params }: { params: Promise<{ id: s
{ label: 'USt-IdNr.', key: 'vat_id', span: false },
{ label: 'Vorname', key: 'first_name', span: false },
{ label: 'Nachname', key: 'last_name', span: false },
{ label: 'E-Mail', key: 'email', span: true },
{ label: 'Straße & Hausnummer', key: 'street', span: true },
{ label: 'PLZ', key: 'zip', span: false },
{ label: 'Ort', key: 'city', span: false },

View File

@@ -14,7 +14,7 @@ export default function NewCustomerPage() {
const router = useRouter()
const [saving, setSaving] = useState(false)
const [form, setForm] = useState({
company_name: '', vat_id: '', first_name: '', last_name: '', street: '', zip: '', city: '',
company_name: '', vat_id: '', first_name: '', last_name: '', street: '', zip: '', city: '', email: '',
})
const handleSave = async () => {
@@ -58,6 +58,7 @@ export default function NewCustomerPage() {
{ label: 'USt-IdNr.', key: 'vat_id', span: false, placeholder: 'DE123456789' },
{ label: 'Vorname', key: 'first_name', span: false, placeholder: '' },
{ label: 'Nachname', key: 'last_name', span: false, placeholder: '' },
{ label: 'E-Mail', key: 'email', span: true, placeholder: 'kunde@beispiel.de' },
{ label: 'Straße & Hausnummer', key: 'street', span: true, placeholder: 'Musterstraße 1' },
{ label: 'PLZ', key: 'zip', span: false, placeholder: '12345' },
{ label: 'Ort', key: 'city', span: false, placeholder: 'Musterstadt' },

View File

@@ -169,6 +169,7 @@ export async function adminCreateEndCustomer(data: {
street?: string
zip?: string
city?: string
email?: string
}) {
const admin = createAdminClient()
const { data: dbData, error } = await admin
@@ -182,6 +183,7 @@ export async function adminCreateEndCustomer(data: {
street: data.street || null,
zip: data.zip || null,
city: data.city || null,
email: data.email || null,
}])
.select()
.single()
@@ -198,6 +200,7 @@ export async function adminUpdateEndCustomer(id: string, data: {
street?: string
zip?: string
city?: string
email?: string
}) {
const admin = createAdminClient()
const { data: dbData, error } = await admin
@@ -211,6 +214,7 @@ export async function adminUpdateEndCustomer(id: string, data: {
street: data.street || null,
zip: data.zip || null,
city: data.city || null,
email: data.email || null,
})
.eq('id', id)
.select()

View File

@@ -15,6 +15,7 @@ export type EndCustomerFormData = {
street?: string
zip?: string
city?: string
email?: string
}
// ─── Lesen ───────────────────────────────────────────────────────────────────
@@ -72,6 +73,7 @@ export async function createEndCustomer(formData: EndCustomerFormData): Promise<
street: formData.street || null,
zip: formData.zip || null,
city: formData.city || null,
email: formData.email || null,
}])
.select()
.single()
@@ -101,6 +103,7 @@ export async function updateEndCustomer(id: string, formData: EndCustomerFormDat
street: formData.street || null,
zip: formData.zip || null,
city: formData.city || null,
email: formData.email || null,
})
.eq('id', id)
@@ -134,6 +137,7 @@ export async function anonymizeEndCustomer(id: string): Promise<void> {
street: null,
zip: null,
city: null,
email: null,
is_anonymized: true,
})
.eq('id', id)

View File

@@ -62,6 +62,7 @@ export type EndCustomer = {
street: string | null
zip: string | null
city: string | null
email?: string | null
is_anonymized: boolean
created_at: string
}

View File

@@ -0,0 +1,3 @@
-- Migration: Add email column to end_customers table
ALTER TABLE public.end_customers
ADD COLUMN IF NOT EXISTS email TEXT;