Add campaign scheduling lifecycle jobs
This commit is contained in:
@@ -16,7 +16,9 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { CampaignFormDialog } from "@/components/campaigns/campaign-form-dialog";
|
||||
|
||||
type CampaignsListResult = FunctionReturnType<typeof api.campaigns.list>;
|
||||
type CampaignRunsListResult = FunctionReturnType<typeof api.runs.list>;
|
||||
type CampaignRow = NonNullable<CampaignsListResult>[number];
|
||||
type CampaignRunRow = NonNullable<CampaignRunsListResult>[number];
|
||||
|
||||
type RecurrenceLabel = Record<CampaignRow["recurrence"], string>;
|
||||
type CurrentRunStatusLabel = {
|
||||
@@ -40,6 +42,13 @@ const statusLabel: CurrentRunStatusLabel = {
|
||||
paused: "Pausiert",
|
||||
};
|
||||
|
||||
const stepLabel: Record<string, string> = {
|
||||
campaign_cron_queued: "Cron geplant",
|
||||
campaign_cron_skipped: "Cron übersprungen",
|
||||
campaign_cron_stale_pending: "Timeout bereinigt",
|
||||
lead_discovery: "Lead-Recherche",
|
||||
};
|
||||
|
||||
const dateFormatter = new Intl.DateTimeFormat("de-DE", {
|
||||
dateStyle: "short",
|
||||
timeStyle: "short",
|
||||
@@ -84,6 +93,10 @@ const formatNiche = (campaign: CampaignRow): string => {
|
||||
|
||||
export function CampaignsBoard() {
|
||||
const campaigns = useQuery(api.campaigns.list, { limit: 100 });
|
||||
const recentCampaignRuns = useQuery(api.runs.list, {
|
||||
limit: 8,
|
||||
type: "campaign",
|
||||
});
|
||||
const createCampaign = useMutation(api.campaigns.create);
|
||||
const updateCampaign = useMutation(api.campaigns.update);
|
||||
const setStatus = useMutation(api.campaigns.setStatus);
|
||||
@@ -130,6 +143,10 @@ export function CampaignsBoard() {
|
||||
return [...campaigns].sort((a, b) => b.createdAt - a.createdAt);
|
||||
}, [campaigns]);
|
||||
|
||||
const visibleRuns = useMemo<CampaignRunRow[]>(() => {
|
||||
return recentCampaignRuns ?? [];
|
||||
}, [recentCampaignRuns]);
|
||||
|
||||
const closeDialog = () => {
|
||||
setEditingCampaign(null);
|
||||
setIsFormOpen(false);
|
||||
@@ -343,6 +360,48 @@ export function CampaignsBoard() {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Aktuelle Run-Logs</CardTitle>
|
||||
<CardDescription>
|
||||
Letzte Kampagnenläufe inklusive Cron-Skips und Fehlerhinweisen.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-2 text-sm">
|
||||
{recentCampaignRuns === undefined ? (
|
||||
<Skeleton className="h-16 rounded-lg" />
|
||||
) : visibleRuns.length === 0 ? (
|
||||
<p className="text-muted-foreground">Noch keine Kampagnenläufe.</p>
|
||||
) : (
|
||||
visibleRuns.map((run) => (
|
||||
<div className="rounded-md border p-3" key={run._id}>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<p className="font-medium">
|
||||
{statusLabel[run.status] ?? run.status}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{formatDateTime(run.updatedAt)}
|
||||
</p>
|
||||
</div>
|
||||
<p className="mt-1 text-muted-foreground">
|
||||
{stepLabel[run.currentStep ?? ""] ?? run.currentStep ?? "Schritt offen"}
|
||||
</p>
|
||||
{run.currentStep === "campaign_cron_skipped" ? (
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Cron wurde übersprungen, weil bereits ein Agentenlauf aktiv war.
|
||||
</p>
|
||||
) : null}
|
||||
{run.errorSummary ? (
|
||||
<p className="mt-1 text-xs text-destructive">
|
||||
{run.errorSummary}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user