feat: convert campaign and lead views to cards

This commit is contained in:
2026-06-04 17:11:39 +02:00
parent 59824b7336
commit ca42c8d5a6
5 changed files with 563 additions and 485 deletions

View File

@@ -267,187 +267,81 @@ export function CampaignsBoard() {
</CardHeader>
</Card>
) : (
<>
<div className="hidden overflow-x-auto rounded-lg border bg-card md:block">
<table className="w-full min-w-[820px] border-separate border-spacing-0">
<thead>
<tr className="text-left text-sm text-muted-foreground">
<th className="sticky left-0 bg-card p-3 font-normal">Kampagne</th>
<th className="p-3 font-normal">PLZ / Radius</th>
<th className="p-3 font-normal">Cadence</th>
<th className="p-3 font-normal">Limits</th>
<th className="p-3 font-normal">Status</th>
<th className="p-3 font-normal">Lauf</th>
<th className="p-3 font-normal">Aktionen</th>
</tr>
</thead>
<tbody>
{campaignsSorted.map((campaign) => (
<tr
className="border-t"
key={campaign._id}
<div className="grid gap-3">
{campaignsSorted.map((campaign) => (
<Card key={campaign._id}>
<CardHeader>
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle className="truncate">{campaign.name}</CardTitle>
<CardDescription className="truncate">
{formatNiche(campaign)}
</CardDescription>
</div>
<Badge
variant={campaign.status === "active" ? "default" : "secondary"}
>
<td className="max-w-[220px] p-3 align-top">
<div className="space-y-1">
<p className="truncate font-medium">{campaign.name}</p>
<p className="text-sm text-muted-foreground">
{formatNiche(campaign)}
</p>
</div>
</td>
{campaign.status === "active" ? "Aktiv" : "Pausiert"}
</Badge>
</div>
</CardHeader>
<td className="max-w-[180px] p-3 align-top">
<div className="space-y-1 text-sm text-muted-foreground">
<p className="inline-flex items-center gap-1">
<MapPin className="size-3" />
<span>{campaign.postalCode}</span>
</p>
<p>{campaign.radiusKm} km Umkreis</p>
</div>
</td>
<td className="p-3 align-top">
<span className="rounded-md bg-muted px-2 py-1 text-sm">
{recurrenceLabel[campaign.recurrence]}
</span>
</td>
<td className="p-3 align-top">
<p className="text-sm">
Leads: {campaign.maxNewLeadsPerRun} · Audits:{" "}
{campaign.maxAuditsPerRun}
</p>
</td>
<td className="p-3 align-top">
<Badge
variant={campaign.status === "active" ? "default" : "secondary"}
>
{campaign.status === "active" ? "Aktiv" : "Pausiert"}
</Badge>
</td>
<td className="p-3 align-top">
<div className="space-y-1 text-sm text-muted-foreground">
<p>Letzter Lauf: {formatDateTime(campaign.lastRunAt)}</p>
<p>Nächster Lauf: {formatDateTime(campaign.nextRunAt)}</p>
<p>Run-Status: {statusLabel[campaign.currentRunStatus] ?? campaign.currentRunStatus}</p>
</div>
</td>
<td className="p-3 align-top">
<div className="flex flex-wrap gap-2">
<Button
className="w-full sm:w-auto"
variant="outline"
onClick={() => openEditDialog(campaign)}
disabled={actionBusyId === campaign._id}
>
<Pencil className="size-4" />
Bearbeiten
</Button>
<Button
className="w-full sm:w-auto"
variant="outline"
onClick={() => toggleCampaign(campaign)}
disabled={actionBusyId === campaign._id}
>
<RefreshCcw className="size-4" />
{campaign.status === "active" ? "Pausieren" : "Fortfahren"}
</Button>
<Button
className="w-full sm:w-auto"
onClick={() => runCampaign(campaign)}
disabled={actionBusyId === campaign._id}
>
<Play className="size-4" />
Jetzt ausführen
</Button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
<div className="grid gap-3 md:hidden">
{campaignsSorted.map((campaign) => (
<Card key={campaign._id}>
<CardHeader>
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="min-w-0">
<CardTitle className="truncate">{campaign.name}</CardTitle>
<CardDescription className="truncate">
{formatNiche(campaign)}
</CardDescription>
</div>
<Badge
variant={campaign.status === "active" ? "default" : "secondary"}
>
{campaign.status === "active" ? "Aktiv" : "Pausiert"}
</Badge>
<CardContent className="grid gap-2 text-sm">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="inline-flex items-center gap-1 text-muted-foreground">
<MapPin className="size-3" />
<span>{campaign.postalCode}</span>
</div>
</CardHeader>
<span>{campaign.radiusKm} km</span>
</div>
<Separator className="bg-border" />
<div>
<p>Cadence: {recurrenceLabel[campaign.recurrence]}</p>
<p>
Limits: L {campaign.maxNewLeadsPerRun}, A{" "}
{campaign.maxAuditsPerRun}
</p>
</div>
<div>
<p className="text-muted-foreground">Letzter Lauf: {formatDateTime(campaign.lastRunAt)}</p>
<p className="text-muted-foreground">Nächster Lauf: {formatDateTime(campaign.nextRunAt)}</p>
<p className="text-muted-foreground">
Run-Status: {statusLabel[campaign.currentRunStatus] ?? campaign.currentRunStatus}
</p>
</div>
<CardContent className="grid gap-2 text-sm">
<div className="flex flex-wrap items-center justify-between gap-3">
<div className="inline-flex items-center gap-1 text-muted-foreground">
<MapPin className="size-3" />
<span>{campaign.postalCode}</span>
</div>
<span>{campaign.radiusKm} km</span>
</div>
<Separator className="bg-border" />
<div>
<p>Cadence: {recurrenceLabel[campaign.recurrence]}</p>
<p>
Limits: L {campaign.maxNewLeadsPerRun}, A{" "}
{campaign.maxAuditsPerRun}
</p>
</div>
<div>
<p className="text-muted-foreground">Letzter Lauf: {formatDateTime(campaign.lastRunAt)}</p>
<p className="text-muted-foreground">Nächster Lauf: {formatDateTime(campaign.nextRunAt)}</p>
<p className="text-muted-foreground">
Run-Status: {statusLabel[campaign.currentRunStatus] ?? campaign.currentRunStatus}
</p>
</div>
<div className="grid gap-2">
<Button
variant="outline"
onClick={() => openEditDialog(campaign)}
disabled={actionBusyId === campaign._id}
className="w-full justify-start"
>
<Pencil className="size-4" />
Bearbeiten
</Button>
<Button
variant="outline"
onClick={() => toggleCampaign(campaign)}
disabled={actionBusyId === campaign._id}
className="w-full justify-start"
>
<RefreshCcw className="size-4" />
{campaign.status === "active" ? "Pausieren" : "Fortfahren"}
</Button>
<Button
onClick={() => runCampaign(campaign)}
disabled={actionBusyId === campaign._id}
className="w-full justify-start"
>
<Play className="size-4" />
Jetzt ausführen
</Button>
</div>
</CardContent>
</Card>
))}
</div>
</>
<div className="grid gap-2">
<Button
variant="outline"
onClick={() => openEditDialog(campaign)}
disabled={actionBusyId === campaign._id}
className="w-full justify-start"
>
<Pencil className="size-4" />
Bearbeiten
</Button>
<Button
variant="outline"
onClick={() => toggleCampaign(campaign)}
disabled={actionBusyId === campaign._id}
className="w-full justify-start"
>
<RefreshCcw className="size-4" />
{campaign.status === "active" ? "Pausieren" : "Fortfahren"}
</Button>
<Button
onClick={() => runCampaign(campaign)}
disabled={actionBusyId === campaign._id}
className="w-full justify-start"
>
<Play className="size-4" />
Jetzt ausführen
</Button>
</div>
</CardContent>
</Card>
))}
</div>
)}
</section>
);

View File

@@ -22,7 +22,7 @@ import {
type LeadBlacklistStatus,
} from "@/lib/dashboard-model";
import { Button } from "@/components/ui/button";
import { Card } from "@/components/ui/card";
import { Card, CardHeader } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
@@ -148,59 +148,23 @@ export function LeadsReviewTable() {
<h1 className="text-2xl font-semibold tracking-normal">Leads prüfen</h1>
</div>
<div className="mx-auto w-full max-w-7xl">
<Card className="overflow-hidden">
<div className="overflow-x-auto">
<div className="min-w-[1150px]">
<table className="w-full border-separate border-spacing-0 text-sm">
<thead>
<tr className="text-left text-xs text-muted-foreground">
<th className="p-3 font-normal">Firma / Ort</th>
<th className="p-3 font-normal">Kontakt + Quelle</th>
<th className="p-3 font-normal">Priorität</th>
<th className="p-3 font-normal">Kontaktstatus</th>
<th className="p-3 font-normal">Qualität</th>
<th className="p-3 font-normal">Review-Felder</th>
<th className="p-3 font-normal">Aktionen</th>
</tr>
</thead>
{leads === undefined ? (
<tbody>
<tr>
<td className="p-3" colSpan={7}>
<p className="rounded-md bg-muted p-4 text-sm">
Leads werden geladen
</p>
</td>
</tr>
</tbody>
) : sortedLeads.length === 0 ? (
<tbody>
<tr>
<td className="p-3" colSpan={7}>
<p className="rounded-md border p-4 text-sm text-muted-foreground">
Keine Leads vorhanden. Bitte zuerst eine Kampagne starten
oder importieren.
</p>
</td>
</tr>
</tbody>
) : (
<tbody>
{sortedLeads.map((lead) => (
<LeadReviewRow
key={lead._id}
lead={lead}
onActionMessage={setActionMessage}
/>
))}
</tbody>
)}
</table>
</div>
</div>
</Card>
<div className="mx-auto grid w-full max-w-7xl gap-3">
{leads === undefined ? (
<p className="rounded-md bg-muted p-4 text-sm">Leads werden geladen</p>
) : sortedLeads.length === 0 ? (
<p className="rounded-md border p-4 text-sm text-muted-foreground">
Keine Leads vorhanden. Bitte zuerst eine Kampagne starten oder
importieren.
</p>
) : (
sortedLeads.map((lead) => (
<LeadReviewRow
key={lead._id}
lead={lead}
onActionMessage={setActionMessage}
/>
))
)}
</div>
{actionMessage ? (
@@ -219,6 +183,7 @@ function LeadReviewRow({
lead: LeadRow;
onActionMessage: (value: string) => void;
}) {
const [isExpanded, setIsExpanded] = useState(false);
const [draft, setDraft] = useState<LeadReviewDraft>(() => ({
priority: lead.priority,
contactStatus: lead.contactStatus,
@@ -313,264 +278,290 @@ function LeadReviewRow({
setDraft((current) => ({ ...current, [field]: value }));
};
const detailsId = `lead-review-details-${lead._id}`;
return (
<tr className="border-t">
<td className="max-w-[260px] p-3 align-top">
<p className="font-medium">{lead.companyName}</p>
<p className="mt-1 inline-flex items-center gap-1 truncate text-xs text-muted-foreground">
<Building2 className="size-3 shrink-0" />
<span className="truncate">{lead.niche ?? "Nische offen"}</span>
</p>
<p className="mt-2 inline-flex items-center gap-1 text-xs text-muted-foreground">
<MapPin className="size-3 shrink-0" />
<span>{location}</span>
</p>
{lead.address ? (
<p className="mt-1 max-w-full truncate text-xs text-muted-foreground">
{lead.address}
</p>
) : null}
</td>
<Card>
<CardHeader className="pb-3">
<div className="grid min-w-0 gap-2">
<div className="flex min-w-0 flex-wrap items-start justify-between gap-2">
<div className="min-w-0 flex-1">
<p className="max-w-full truncate font-medium">
{lead.companyName}
</p>
<p className="mt-1 inline-flex items-center gap-1 text-xs text-muted-foreground">
<Building2 className="size-3 shrink-0" />
<span className="inline-flex min-w-0 max-w-full break-words">
{lead.niche ?? "Nische offen"}
</span>
</p>
<p className="mt-2 inline-flex items-center gap-1 text-xs text-muted-foreground">
<MapPin className="size-3 shrink-0" />
<span className="inline-flex min-w-0 max-w-full truncate">
{location}
</span>
</p>
</div>
<td className="max-w-[260px] p-3 align-top">
<p className="inline-flex w-full items-start gap-1 text-sm">
<Mail className="mt-0.5 size-3 shrink-0" />
<span className="min-w-0 break-all">
{lead.email || "Keine E-Mail"}
</span>
</p>
{lead.phone ? (
<p className="mt-2 inline-flex w-full items-start gap-1 text-xs text-muted-foreground">
<Phone className="size-3 shrink-0" />
<span className="break-all">{lead.phone}</span>
</p>
) : null}
<p className="mt-2 text-xs text-muted-foreground">
Quelle: {contactSourceLabel(lead)}
</p>
{lead.websiteDomain ? (
<p className="mt-1 text-xs text-muted-foreground">
Domain: {lead.websiteDomain}
</p>
) : null}
</td>
<p
className={`inline-flex shrink-0 rounded-md border px-2 py-1 text-xs font-medium ${priorityBadgeClass(
draft.priority,
)}`}
>
{getLeadPriorityLabel(draft.priority)}
</p>
</div>
<td className="p-3 align-top">
<p
className={`inline-flex rounded-md border px-2 py-1 text-xs font-medium ${priorityBadgeClass(
draft.priority,
)}`}
<div className="grid min-w-0 gap-1 text-xs text-muted-foreground">
<p className="inline-flex min-w-0 items-center gap-1">
<Mail className="size-3 shrink-0" />
<span className="max-w-full min-w-0 break-all">
{lead.email || "Keine E-Mail"}
</span>
</p>
{lead.phone ? (
<p className="inline-flex min-w-0 items-center gap-1">
<Phone className="size-3 shrink-0" />
<span className="max-w-full min-w-0 break-all">{lead.phone}</span>
</p>
) : null}
<p className="truncate max-w-full">
Quelle: {contactSourceLabel(lead)}
</p>
{lead.websiteDomain ? (
<p className="truncate max-w-full">Domain: {lead.websiteDomain}</p>
) : null}
</div>
</div>
</CardHeader>
<div className="border-t p-4 pt-3">
<Button
type="button"
variant="outline"
onClick={() => setIsExpanded((previous) => !previous)}
aria-expanded={isExpanded}
aria-controls={detailsId}
size="sm"
>
{getLeadPriorityLabel(draft.priority)}
</p>
<div className="mt-2 max-w-[160px]">
<Select
value={draft.priority}
onValueChange={(nextPriority) =>
updateDraft("priority", nextPriority as LeadPriority)
}
>
<SelectTrigger>
<SelectValue placeholder="Priorität" />
</SelectTrigger>
<SelectContent>
{leadPriorityOptions.map((value) => (
<SelectItem value={value} key={value}>
{getLeadPriorityLabel(value)}
</SelectItem>
))}
</SelectContent>
</Select>
{isExpanded ? "Weniger anzeigen" : "Mehr anzeigen"}
</Button>
</div>
<div
id={detailsId}
className="grid gap-3 border-t p-4"
hidden={!isExpanded}
>
<div className="grid gap-3 xl:grid-cols-2">
<section className="grid gap-2">
<div>
<p className="text-xs text-muted-foreground">Priorität</p>
<div className="mt-2">
<Select
value={draft.priority}
onValueChange={(nextPriority) =>
updateDraft("priority", nextPriority as LeadPriority)
}
>
<SelectTrigger>
<SelectValue placeholder="Priorität" />
</SelectTrigger>
<SelectContent>
{leadPriorityOptions.map((value) => (
<SelectItem value={value} key={value}>
{getLeadPriorityLabel(value)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div>
<p className="text-xs text-muted-foreground">Kontaktstatus</p>
<div className="mt-2">
<Select
value={draft.contactStatus}
onValueChange={(nextStatus) =>
updateDraft("contactStatus", nextStatus as LeadContactStatus)
}
>
<SelectTrigger>
<SelectValue placeholder="Kontaktstatus" />
</SelectTrigger>
<SelectContent>
{leadContactStatusOptions.map((status) => (
<SelectItem value={status} key={status}>
{getLeadContactStatusLabel(status)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</section>
<section className="grid gap-2">
<div>
<p className="text-xs text-muted-foreground">Prioritätsgrund</p>
<Input
value={draft.priorityReason}
onChange={(event) => {
updateDraft("priorityReason", event.target.value);
}}
/>
</div>
<div>
<p className="mt-2 text-xs text-muted-foreground">
Kontaktstatus-Notiz
</p>
<Input
value={draft.contactStatusReason}
onChange={(event) => {
updateDraft("contactStatusReason", event.target.value);
}}
/>
</div>
<div>
<p className="mt-2 text-xs text-muted-foreground">Notiz</p>
<Input
value={draft.notes}
onChange={(event) => {
updateDraft("notes", event.target.value);
}}
/>
</div>
<div className="mt-2 space-y-1 text-xs text-muted-foreground">
{reasonParts.length === 0 ? (
<p>Keine Zusatzhinweise</p>
) : (
reasonParts.map((reason) => <p key={reason}> {reason}</p>)
)}
</div>
</section>
<section className="grid gap-2">
<div>
<p className="text-xs text-muted-foreground">Review-E-Mail</p>
<Input
value={draft.reviewEmail}
onChange={(event) => {
updateDraft("reviewEmail", event.target.value);
}}
/>
</div>
<div>
<p className="mt-2 text-xs text-muted-foreground">Review-Quelle</p>
<Input
value={draft.reviewEmailSource}
onChange={(event) => {
updateDraft("reviewEmailSource", event.target.value);
}}
/>
</div>
<div>
<p className="mt-2 text-xs text-muted-foreground">Ansprechperson</p>
<Input
value={draft.reviewContactPerson}
onChange={(event) => {
updateDraft("reviewContactPerson", event.target.value);
}}
/>
</div>
<label className="mt-2 inline-flex items-center gap-2 text-xs text-muted-foreground">
<Switch
checked={draft.reviewIsBusinessContactAddress}
onCheckedChange={(checked) => {
updateDraft("reviewIsBusinessContactAddress", checked);
}}
/>
Genannte E-Mail als Business-Kontakt
</label>
</section>
<section className="grid gap-2">
<div>
<p className="text-xs text-muted-foreground">Duplikatstatus</p>
<div className="mt-2">
<Select
value={draft.duplicateStatus}
onValueChange={(nextStatus) =>
updateDraft("duplicateStatus", nextStatus as LeadDuplicateStatus)
}
>
<SelectTrigger>
<SelectValue placeholder="Duplikatstatus" />
</SelectTrigger>
<SelectContent>
{leadDuplicateStatusOptions.map((status) => (
<SelectItem value={status} key={status}>
{getLeadDuplicateStatusLabel(status)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div>
<label className="text-xs text-muted-foreground">Sperrstatus</label>
<div className="mt-2">
<Select
value={draft.blacklistStatus}
onValueChange={(nextStatus) =>
updateDraft("blacklistStatus", nextStatus as LeadBlacklistStatus)
}
>
<SelectTrigger>
<SelectValue placeholder="Sperrstatus" />
</SelectTrigger>
<SelectContent>
{leadBlacklistStatusOptions.map((status) => (
<SelectItem value={status} key={status}>
{getLeadBlacklistStatusLabel(status)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div className="mt-2 grid gap-2 sm:grid-cols-2">
<Badge
variant={duplicateBadgeVariant(draft.duplicateStatus)}
title={lead.duplicateReason ?? undefined}
>
{getLeadDuplicateStatusLabel(draft.duplicateStatus)}
</Badge>
<Badge
variant={lead.blacklistStatus === "blocked" ? "destructive" : "secondary"}
>
{getLeadBlacklistStatusLabel(lead.blacklistStatus)}
</Badge>
</div>
<div className="mt-2 grid gap-2 sm:grid-cols-2">
<Button onClick={saveRow} disabled={isSaving || isBlocking} size="sm">
<span>Speichern</span>
</Button>
<Button
variant="destructive"
onClick={blockLead}
disabled={isSaving || isBlocking}
size="sm"
>
<ShieldAlert className="size-4" />
Sperren
</Button>
</div>
{rowMessage ? (
<p className="text-xs text-muted-foreground">{rowMessage}</p>
) : null}
</section>
</div>
</td>
<td className="p-3 align-top">
<Badge variant="outline">
{getLeadContactStatusLabel(draft.contactStatus)}
</Badge>
<div className="mt-2 max-w-[180px]">
<Select
value={draft.contactStatus}
onValueChange={(nextStatus) =>
updateDraft("contactStatus", nextStatus as LeadContactStatus)
}
>
<SelectTrigger>
<SelectValue placeholder="Kontaktstatus" />
</SelectTrigger>
<SelectContent>
{leadContactStatusOptions.map((status) => (
<SelectItem value={status} key={status}>
{getLeadContactStatusLabel(status)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</td>
<td className="max-w-[220px] p-3 align-top">
<div className="grid gap-2">
<p className="text-xs text-muted-foreground">Prioritätsgrund</p>
<Input
value={draft.priorityReason}
onChange={(event) => {
updateDraft("priorityReason", event.target.value);
}}
/>
</div>
<div className="mt-2 grid gap-2">
<p className="text-xs text-muted-foreground">Kontaktstatus-Notiz</p>
<Input
value={draft.contactStatusReason}
onChange={(event) => {
updateDraft("contactStatusReason", event.target.value);
}}
/>
</div>
<div className="mt-2 grid gap-2">
<p className="text-xs text-muted-foreground">Notiz</p>
<Input
value={draft.notes}
onChange={(event) => {
updateDraft("notes", event.target.value);
}}
/>
</div>
<div className="mt-3 flex flex-wrap gap-2">
<Badge
variant={duplicateBadgeVariant(draft.duplicateStatus)}
title={lead.duplicateReason ?? undefined}
>
{getLeadDuplicateStatusLabel(draft.duplicateStatus)}
</Badge>
<Badge
variant={lead.blacklistStatus === "blocked" ? "destructive" : "secondary"}
>
{getLeadBlacklistStatusLabel(lead.blacklistStatus)}
</Badge>
</div>
<div className="mt-2 space-y-1 text-xs text-muted-foreground">
{reasonParts.length === 0 ? (
<p>Keine Zusatzhinweise</p>
) : (
reasonParts.map((reason) => <p key={reason}> {reason}</p>)
)}
</div>
</td>
<td className="min-w-[260px] p-3 align-top">
<div className="grid gap-2">
<p className="text-xs text-muted-foreground">Review-E-Mail</p>
<Input
value={draft.reviewEmail}
onChange={(event) => {
updateDraft("reviewEmail", event.target.value);
}}
/>
</div>
<div className="mt-2 grid gap-2">
<p className="text-xs text-muted-foreground">Review-Quelle</p>
<Input
value={draft.reviewEmailSource}
onChange={(event) => {
updateDraft("reviewEmailSource", event.target.value);
}}
/>
</div>
<div className="mt-2 grid gap-2">
<p className="text-xs text-muted-foreground">Ansprechperson</p>
<Input
value={draft.reviewContactPerson}
onChange={(event) => {
updateDraft("reviewContactPerson", event.target.value);
}}
/>
</div>
<label className="mt-3 inline-flex items-center gap-2 text-xs text-muted-foreground">
<Switch
checked={draft.reviewIsBusinessContactAddress}
onCheckedChange={(checked) => {
updateDraft("reviewIsBusinessContactAddress", checked);
}}
/>
Genannte E-Mail als Business-Kontakt
</label>
<div className="mt-3 grid gap-2">
<p className="text-xs text-muted-foreground">Duplikatstatus</p>
<Select
value={draft.duplicateStatus}
onValueChange={(nextStatus) =>
updateDraft("duplicateStatus", nextStatus as LeadDuplicateStatus)
}
>
<SelectTrigger>
<SelectValue placeholder="Duplikatstatus" />
</SelectTrigger>
<SelectContent>
{leadDuplicateStatusOptions.map((status) => (
<SelectItem value={status} key={status}>
{getLeadDuplicateStatusLabel(status)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="mt-2">
<label className="text-xs text-muted-foreground">Sperrstatus</label>
<Select
value={draft.blacklistStatus}
onValueChange={(nextStatus) =>
updateDraft("blacklistStatus", nextStatus as LeadBlacklistStatus)
}
>
<SelectTrigger>
<SelectValue placeholder="Sperrstatus" />
</SelectTrigger>
<SelectContent>
{leadBlacklistStatusOptions.map((status) => (
<SelectItem value={status} key={status}>
{getLeadBlacklistStatusLabel(status)}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</td>
<td className="max-w-[170px] p-3 align-top">
<div className="grid gap-2">
<Button
onClick={saveRow}
disabled={isSaving || isBlocking}
size="sm"
>
<span>Speichern</span>
</Button>
<Button
variant="destructive"
onClick={blockLead}
disabled={isSaving || isBlocking}
size="sm"
>
<ShieldAlert className="size-4" />
Sperren
</Button>
</div>
{rowMessage ? (
<p className="mt-2 text-xs text-muted-foreground">{rowMessage}</p>
) : null}
</td>
</tr>
</div>
</Card>
);
}