feat: convert campaign and lead views to cards
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user