diff --git a/shop/app/admin/companies/[id]/customers/page.tsx b/shop/app/admin/companies/[id]/customers/page.tsx new file mode 100644 index 0000000..9ca347e --- /dev/null +++ b/shop/app/admin/companies/[id]/customers/page.tsx @@ -0,0 +1,444 @@ +'use client' + +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, + Users, + Briefcase, + AlertCircle +} 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 { + getCompanyDetails, + getEndCustomersByCompanyWithPartners, + getCompanyPartners, + adminCreateEndCustomer, + adminUpdateEndCustomer, + adminDeleteEndCustomer +} from '@/lib/actions/companies' + +export default function CompanyCustomersPage() { + const params = useParams() + const router = useRouter() + const companyId = params.id as string + + const [company, setCompany] = useState(null) + const [customers, setCustomers] = useState([]) + const [partners, setPartners] = useState([]) + const [loading, setLoading] = useState(true) + const [loadingAction, setLoadingAction] = useState(false) + const [searchQuery, setSearchQuery] = useState('') + + // Dialog State + const [isOpen, setIsOpen] = useState(false) + const [editingCustomer, setEditingCustomer] = useState(null) + + // Form State + const [companyName, setCompanyName] = useState('') + const [firstName, setFirstName] = useState('') + const [lastName, setLastName] = useState('') + const [street, setStreet] = useState('') + const [zip, setZip] = useState('') + const [city, setCity] = useState('') + const [vatId, setVatId] = useState('') + const [partnerId, setPartnerId] = useState('') + const [error, setError] = useState(null) + + const loadData = async () => { + setLoading(true) + try { + const [compDetails, custData, partData] = await Promise.all([ + getCompanyDetails(companyId), + getEndCustomersByCompanyWithPartners(companyId), + getCompanyPartners(companyId) + ]) + setCompany(compDetails) + setCustomers(custData) + setPartners(partData) + } catch (err) { + console.error('Fehler beim Laden der Daten:', err) + } finally { + setLoading(false) + } + } + + useEffect(() => { + if (companyId) { + loadData() + } + }, [companyId]) + + const handleOpenDialog = (customer: any = null) => { + setEditingCustomer(customer) + setCompanyName(customer ? customer.company_name || '' : '') + setFirstName(customer ? customer.first_name || '' : '') + setLastName(customer ? customer.last_name || '' : '') + setStreet(customer ? customer.street || '' : '') + setZip(customer ? customer.zip || '' : '') + setCity(customer ? customer.city || '' : '') + setVatId(customer ? customer.vat_id || '' : '') + setPartnerId(customer ? customer.partner_id || '' : (partners.length > 0 ? partners[0].id : '')) + setError(null) + setIsOpen(true) + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + if (!partnerId) { + setError('Bitte weisen Sie einen Partner zu.') + return + } + setLoadingAction(true) + setError(null) + try { + const payload = { + partner_id: partnerId, + company_name: companyName.trim(), + first_name: firstName.trim() || undefined, + last_name: lastName.trim() || undefined, + street: street.trim() || undefined, + zip: zip.trim() || undefined, + city: city.trim() || undefined, + vat_id: vatId.trim() || undefined, + } + + if (editingCustomer) { + await adminUpdateEndCustomer(editingCustomer.id, payload) + } else { + await adminCreateEndCustomer(payload) + } + setIsOpen(false) + loadData() + } catch (err: any) { + setError(err.message || 'Fehler beim Speichern des Endkunden.') + } finally { + setLoadingAction(false) + } + } + + const handleDelete = async (id: string, name: string) => { + if (!confirm(`Endkunden "${name}" wirklich löschen?`)) return + setLoadingAction(true) + try { + await adminDeleteEndCustomer(id) + loadData() + } catch (err) { + console.error('Fehler beim Löschen des Endkunden:', err) + } finally { + setLoadingAction(false) + } + } + + const filteredCustomers = customers.filter((c) => { + const q = searchQuery.toLowerCase() + const nameMatch = c.company_name?.toLowerCase().includes(q) + 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 + }) + + return ( +
+ {/* Back Link */} +
+ + + Zurück zur Firmenverwaltung + +
+ + {/* Header */} +
+
+

+ + Endkunden von {company ? company.name : '...'} +

+

+ Hier verwalten Sie alle Endkunden, die Partnern dieser Firma zugeordnet sind. +

+
+ +
+ + {/* Warning if no partners exist */} + {!loading && partners.length === 0 && ( +
+ +
+

Keine Partner vorhanden

+

+ Diese Firma hat aktuell keine zugewiesenen Partner (Benutzer mit der Rolle "partner"). + Sie müssen zuerst in der Benutzerverwaltung Partner erstellen und dieser Firma zuweisen, um Endkunden anzulegen. +

+
+
+ )} + + {/* Table Section */} +
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-9 pr-4 py-2 text-sm bg-slate-50 dark:bg-white/5 border border-slate-200 dark:border-white/10 rounded-md text-slate-900 dark:text-slate-200 placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 transition-all" + /> +
+ + {loading ? ( +
+ + Lade Endkunden... +
+ ) : filteredCustomers.length === 0 ? ( +
+ + Keine Endkunden gefunden. +
+ ) : ( + + + + + + + + + + + + {filteredCustomers.map((customer) => ( + + + + + + + + ))} + +
Endkunde (Firma / Kontakt)LieferadresseUSt-IDZugeordneter PartnerAktionen
+
+ {customer.company_name} +
+ {(customer.first_name || customer.last_name) && ( +
+ + {customer.first_name || ''} {customer.last_name || ''} +
+ )} +
+ {customer.street ? ( +
+ + + {customer.street}, {customer.zip} {customer.city} + +
+ ) : ( + + )} +
+ {customer.vat_id || } + + + {customer.partner_name || 'Unbekannt'} + + +
+ + +
+
+ )} +
+ + {/* Dialog: Endkunde anlegen / bearbeiten */} + + + + + {editingCustomer ? 'Endkunden bearbeiten' : 'Neuen Endkunden anlegen'} + + + Geben Sie die Firmendaten und optional Kontaktdaten und Adresse des Endkunden an. + + +
+
+ + setCompanyName(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+ +
+
+ + setFirstName(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+
+ + setLastName(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+
+ +
+ + setStreet(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+ +
+
+ + setZip(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+
+ + setCity(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+
+ +
+
+ + setVatId(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+ +
+ + +
+
+ + {error && ( +

{error}

+ )} + + + + + +
+
+
+
+ ) +} diff --git a/shop/app/admin/companies/page.tsx b/shop/app/admin/companies/page.tsx index 5915463..3f3f69f 100644 --- a/shop/app/admin/companies/page.tsx +++ b/shop/app/admin/companies/page.tsx @@ -1,6 +1,7 @@ 'use client' import { useState, useEffect } from 'react' +import Link from 'next/link' import { Building2, Plus, Trash2, Loader2, Pencil, Mail, MapPin, Search, Users } from 'lucide-react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' @@ -13,7 +14,7 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog' -import { getCompanies, createCompany, updateCompany, deleteCompany, getEndCustomersByCompany } from '@/lib/actions/companies' +import { getCompanies, createCompany, updateCompany, deleteCompany } from '@/lib/actions/companies' export default function CompaniesPage() { const [companies, setCompanies] = useState([]) @@ -32,10 +33,7 @@ export default function CompaniesPage() { const [deletingId, setDeletingId] = useState(null) const [searchQuery, setSearchQuery] = useState('') - const [isCustomersOpen, setIsCustomersOpen] = useState(false) - const [selectedCompanyForCustomers, setSelectedCompanyForCustomers] = useState(null) - const [companyCustomers, setCompanyCustomers] = useState([]) - const [loadingCustomers, setLoadingCustomers] = useState(false) + const loadCompanies = async () => { setLoading(true) @@ -90,19 +88,7 @@ export default function CompaniesPage() { } } - const handleShowCustomers = async (company: any) => { - setSelectedCompanyForCustomers(company) - setIsCustomersOpen(true) - setLoadingCustomers(true) - try { - const data = await getEndCustomersByCompany(company.id) - setCompanyCustomers(data) - } catch (err) { - console.error('Fehler beim Laden der Endkunden:', err) - } finally { - setLoadingCustomers(false) - } - } + const handleDelete = async (id: string, firmName: string) => { if (!confirm(`Firma "${firmName}" wirklich löschen? Alle zugewiesenen Partner werden getrennt.`)) return @@ -211,11 +197,13 @@ export default function CompaniesPage() { - - - + ) } diff --git a/shop/lib/actions/companies.ts b/shop/lib/actions/companies.ts index ccb6a16..4001c1b 100644 --- a/shop/lib/actions/companies.ts +++ b/shop/lib/actions/companies.ts @@ -76,3 +76,156 @@ export async function getEndCustomersByCompany(companyId: string) { return customers || [] } + +export async function getCompanyDetails(companyId: string) { + const admin = createAdminClient() + const { data, error } = await admin.from('companies').select('*').eq('id', companyId).single() + if (error) throw error + return data +} + +export async function getEndCustomersByCompanyWithPartners(companyId: string) { + const admin = createAdminClient() + + // 1. Get all partners of this company + const { data: partners, error: partnersError } = await admin + .from('users') + .select('id') + .eq('company_id', companyId) + if (partnersError) throw partnersError + + if (!partners || partners.length === 0) return [] + + const partnerIds = partners.map((p) => p.id) + + // 2. Get all end customers belonging to these partners + const { data: customers, error: customersError } = await admin + .from('end_customers') + .select('*') + .in('partner_id', partnerIds) + if (customersError) throw customersError + + // 3. Get profiles of all these partners to resolve their names + const { data: profiles, error: profilesError } = await admin + .from('profiles') + .select('id, first_name, last_name') + .in('id', partnerIds) + if (profilesError) throw profilesError + + // Get user emails + const { data: { users }, error: authError } = await admin.auth.admin.listUsers() + const emailMap = new Map((users || []).map(u => [u.id, u.email || ''])) + + const profileMap = new Map((profiles || []).map((p) => [p.id, p])) + + return customers.map((c) => { + const prof = profileMap.get(c.partner_id) + return { + ...c, + partner_name: prof ? `${prof.first_name || ''} ${prof.last_name || ''}`.trim() || emailMap.get(c.partner_id) || 'Unbekannt' : emailMap.get(c.partner_id) || 'Unbekannt', + } + }) +} + +export async function getCompanyPartners(companyId: string) { + const admin = createAdminClient() + const { data: usersData, error: usersError } = await admin + .from('users') + .select('id, role') + .eq('company_id', companyId) + if (usersError) throw usersError + + if (!usersData || usersData.length === 0) return [] + + const userIds = usersData.map(u => u.id) + + const { data: profiles, error: profilesError } = await admin + .from('profiles') + .select('id, first_name, last_name') + .in('id', userIds) + if (profilesError) throw profilesError + + const { data: { users }, error: authError } = await admin.auth.admin.listUsers() + const emailMap = new Map((users || []).map(u => [u.id, u.email || ''])) + + const profileMap = new Map((profiles || []).map(p => [p.id, p])) + + return usersData.map(u => { + const prof = profileMap.get(u.id) + return { + id: u.id, + email: emailMap.get(u.id) || '', + name: prof ? `${prof.first_name || ''} ${prof.last_name || ''}`.trim() || emailMap.get(u.id) || 'Unbekannt' : emailMap.get(u.id) || 'Unbekannt', + } + }) +} + +export async function adminCreateEndCustomer(data: { + partner_id: string + company_name: string + vat_id?: string + first_name?: string + last_name?: string + street?: string + zip?: string + city?: string +}) { + const admin = createAdminClient() + const { data: dbData, error } = await admin + .from('end_customers') + .insert([{ + partner_id: data.partner_id, + company_name: data.company_name, + vat_id: data.vat_id || null, + first_name: data.first_name || null, + last_name: data.last_name || null, + street: data.street || null, + zip: data.zip || null, + city: data.city || null, + }]) + .select() + .single() + if (error) throw error + return dbData +} + +export async function adminUpdateEndCustomer(id: string, data: { + partner_id: string + company_name: string + vat_id?: string + first_name?: string + last_name?: string + street?: string + zip?: string + city?: string +}) { + const admin = createAdminClient() + const { data: dbData, error } = await admin + .from('end_customers') + .update({ + partner_id: data.partner_id, + company_name: data.company_name, + vat_id: data.vat_id || null, + first_name: data.first_name || null, + last_name: data.last_name || null, + street: data.street || null, + zip: data.zip || null, + city: data.city || null, + }) + .eq('id', id) + .select() + .single() + if (error) throw error + return dbData +} + +export async function adminDeleteEndCustomer(id: string) { + const admin = createAdminClient() + const { error } = await admin + .from('end_customers') + .delete() + .eq('id', id) + if (error) throw error + return true +} +