Files
Lageplan/src/components/admin/org-tab.tsx

247 lines
8.4 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { useToast } from '@/components/ui/use-toast'
import {
Building2, Upload, X, Loader2, Shield, Trash2, AlertTriangle,
} from 'lucide-react'
interface OrgTabProps {
tenantId?: string | null
}
export function OrgTab({ tenantId }: OrgTabProps) {
const { toast } = useToast()
const [loading, setLoading] = useState(true)
const [saving, setSaving] = useState(false)
const [uploadingLogo, setUploadingLogo] = useState(false)
const [tenant, setTenant] = useState<any>(null)
// Editable fields
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [contactEmail, setContactEmail] = useState('')
const [contactPhone, setContactPhone] = useState('')
const [address, setAddress] = useState('')
const fetchTenant = async () => {
setLoading(true)
try {
const res = await fetch('/api/tenant/info')
if (res.ok) {
const data = await res.json()
const t = data.tenant
if (t) {
setTenant(t)
setName(t.name || '')
setDescription(t.description || '')
setContactEmail(t.contactEmail || '')
setContactPhone(t.contactPhone || '')
setAddress(t.address || '')
}
}
} catch (e) {
console.error('Failed to load tenant info:', e)
} finally {
setLoading(false)
}
}
useEffect(() => {
fetchTenant()
}, [tenantId])
const handleSave = async () => {
setSaving(true)
try {
const res = await fetch('/api/tenant/info', {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: name.trim(),
description: description.trim() || null,
contactEmail: contactEmail.trim() || null,
contactPhone: contactPhone.trim() || null,
address: address.trim() || null,
}),
})
if (res.ok) {
toast({ title: 'Organisation aktualisiert' })
fetchTenant()
} else {
const err = await res.json()
toast({ title: 'Fehler', description: err.error || 'Speichern fehlgeschlagen', variant: 'destructive' })
}
} catch {
toast({ title: 'Fehler', description: 'Verbindungsfehler', variant: 'destructive' })
} finally {
setSaving(false)
}
}
const handleLogoUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files?.[0]) return
setUploadingLogo(true)
try {
const formData = new FormData()
formData.append('logo', e.target.files[0])
const res = await fetch('/api/tenant/logo', { method: 'POST', body: formData })
if (res.ok) {
toast({ title: 'Logo hochgeladen' })
fetchTenant()
} else {
const data = await res.json()
toast({ title: 'Fehler', description: data.error || 'Upload fehlgeschlagen', variant: 'destructive' })
}
} catch {
toast({ title: 'Fehler', description: 'Upload fehlgeschlagen', variant: 'destructive' })
} finally {
setUploadingLogo(false)
e.target.value = ''
}
}
const handleLogoDelete = async () => {
try {
const res = await fetch('/api/tenant/logo', { method: 'DELETE' })
if (res.ok) {
toast({ title: 'Logo entfernt' })
fetchTenant()
}
} catch {
toast({ title: 'Fehler', variant: 'destructive' })
}
}
if (loading) {
return (
<div className="flex items-center justify-center py-12">
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
</div>
)
}
if (!tenant) {
return (
<div className="text-center text-muted-foreground py-12">
Keine Organisation zugeordnet.
</div>
)
}
return (
<div className="space-y-6 max-w-2xl">
{/* Logo */}
<div className="border rounded-lg p-5">
<h3 className="font-semibold text-base mb-3 flex items-center gap-2">
<Building2 className="w-4 h-4" />
Logo
</h3>
<div className="flex items-center gap-4">
<div className="w-20 h-20 rounded-lg border bg-muted flex items-center justify-center overflow-hidden shrink-0">
{tenant.logoUrl ? (
<img
src={tenant.logoUrl.startsWith('/') ? tenant.logoUrl : `/api/tenant/logo/serve`}
alt="Logo"
className="w-full h-full object-contain"
/>
) : (
<Building2 className="w-10 h-10 text-muted-foreground/40" />
)}
</div>
<div className="space-y-1.5">
<div className="flex gap-2">
<Button variant="outline" size="sm" className="relative" disabled={uploadingLogo}>
{uploadingLogo ? <Loader2 className="w-3.5 h-3.5 mr-1 animate-spin" /> : <Upload className="w-3.5 h-3.5 mr-1" />}
{tenant.logoUrl ? 'Ändern' : 'Hochladen'}
<input
type="file"
accept="image/png,image/jpeg,image/svg+xml,image/webp"
onChange={handleLogoUpload}
className="absolute inset-0 opacity-0 cursor-pointer"
/>
</Button>
{tenant.logoUrl && (
<Button variant="ghost" size="sm" className="text-destructive" onClick={handleLogoDelete}>
<X className="w-3.5 h-3.5 mr-1" /> Entfernen
</Button>
)}
</div>
<p className="text-[11px] text-muted-foreground">PNG, JPEG, SVG oder WebP, max. 2 MB. Wird auch im Rapport angezeigt.</p>
</div>
</div>
</div>
{/* Organisation Details */}
<div className="border rounded-lg p-5 space-y-4">
<h3 className="font-semibold text-base mb-1 flex items-center gap-2">
<Shield className="w-4 h-4" />
Stammdaten
</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-xs">Name</Label>
<Input value={name} onChange={e => setName(e.target.value)} />
</div>
<div>
<Label className="text-xs">Slug</Label>
<Input value={tenant.slug} disabled className="font-mono bg-muted" />
</div>
</div>
<div>
<Label className="text-xs">Beschreibung</Label>
<Input value={description} onChange={e => setDescription(e.target.value)} placeholder="z.B. Feuerwehr Musterstadt" />
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label className="text-xs">Kontakt E-Mail</Label>
<Input type="email" value={contactEmail} onChange={e => setContactEmail(e.target.value)} placeholder="kontakt@feuerwehr.ch" />
</div>
<div>
<Label className="text-xs">Kontakt Telefon</Label>
<Input value={contactPhone} onChange={e => setContactPhone(e.target.value)} placeholder="+41 ..." />
</div>
</div>
<div>
<Label className="text-xs">Adresse</Label>
<Input value={address} onChange={e => setAddress(e.target.value)} placeholder="Strasse, PLZ Ort" />
</div>
<Button onClick={handleSave} disabled={saving || !name.trim()}>
{saving ? <Loader2 className="w-4 h-4 mr-1.5 animate-spin" /> : null}
Speichern
</Button>
</div>
{/* Info (read-only) */}
<div className="border rounded-lg p-5">
<h3 className="font-semibold text-base mb-3">Übersicht</h3>
<div className="grid grid-cols-2 gap-3 text-sm">
<div>
<span className="text-muted-foreground">Plan:</span>
<span className="ml-2">{tenant.plan}</span>
</div>
<div>
<span className="text-muted-foreground">Status:</span>
<span className="ml-2">{tenant.subscriptionStatus}</span>
</div>
{tenant._count && (
<>
<div>
<span className="text-muted-foreground">Benutzer:</span>
<span className="ml-2">{tenant._count.memberships}</span>
</div>
<div>
<span className="text-muted-foreground">Einsätze:</span>
<span className="ml-2">{tenant._count.projects}</span>
</div>
</>
)}
</div>
</div>
</div>
)
}