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

@@ -0,0 +1,30 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import test from "node:test";
const campaignsBoardPath = join(
process.cwd(),
"components",
"campaigns",
"campaigns-board.tsx",
);
test("campaign board renders campaigns as responsive cards", async () => {
const source = await readFile(campaignsBoardPath, "utf8");
assert.doesNotMatch(source, /<table\b/i);
assert.doesNotMatch(source, /<thead\b/i);
assert.doesNotMatch(source, /<tbody\b/i);
assert.doesNotMatch(source, /<tr\b/i);
assert.doesNotMatch(source, /<td\b/i);
assert.doesNotMatch(source, /<th\b/i);
assert.doesNotMatch(source, /md:hidden/i);
assert.doesNotMatch(source, /md:block/i);
assert.match(source, /className="grid gap-3"/);
assert.match(source, /openEditDialog\(campaign\)/);
assert.match(source, /toggleCampaign\(campaign\)/);
assert.match(source, /runCampaign\(campaign\)/);
});

View File

@@ -0,0 +1,112 @@
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import test from "node:test";
const leadsReviewPath = join(
process.cwd(),
"components",
"leads",
"leads-review-table.tsx",
);
test("LeadsReviewTable uses compact card summaries with expandable review details", async () => {
const source = await readFile(leadsReviewPath, "utf8");
assert.doesNotMatch(source, /<table\b/i);
assert.doesNotMatch(source, /<thead\b/i);
assert.doesNotMatch(source, /<tbody\b/i);
assert.doesNotMatch(source, /<tr\b/i);
assert.doesNotMatch(source, /<td\b/i);
assert.doesNotMatch(source, /<th\b/i);
assert.doesNotMatch(source, /min-w-\[/i);
assert.match(source, /Mehr anzeigen/);
assert.match(source, /Weniger anzeigen/);
assert.match(source, /aria-expanded=\{[^}]+\}/);
assert.match(source, /aria-controls=\{[^}]+\}/);
assert.match(source, /id=\{[^}]+\}/);
assert.match(
source,
/aria-expanded=\{[^}]+\}[\s\S]{0,160}aria-controls=\{[^}]+\}[\s\S]{0,160}(Mehr anzeigen|Weniger anzeigen)/i,
);
assert.match(
source,
/hidden=\{!?isExpanded\}/,
);
const companyNameMatch = source.match(
/<p className="([^"]+)">\s*\{lead\.companyName\}\s*<\/p>/,
);
assert.ok(
companyNameMatch !== null &&
/(?:^|\s)(truncate|max-w-full|min-w-0|break-words)(?:\s|$)/.test(
companyNameMatch[1],
),
"Company name should use overflow-safe text classes in compact card.",
);
const nicheMatch = source.match(
/lead\.niche\s+\?\?\s+"Nische offen"\}\s*<\/span>/,
);
assert.ok(
nicheMatch !== null,
"Niche rendering should still be asserted in test fixture.",
);
const nicheContainerMatch = source.match(
/<span className="([^"]+)">\s*\{lead\.niche\s+\?\?\s+"Nische offen"\}\s*<\/span>/,
);
assert.ok(
nicheContainerMatch !== null &&
/(?:^|\s)(truncate|max-w-full|break-all|break-words)(?:\s|$)/.test(
nicheContainerMatch[1],
),
"Niche should use overflow-safe text classes in compact card.",
);
const locationMatch = source.match(/\{location\}/);
assert.ok(
locationMatch !== null,
"Location rendering should still be present in compact card.",
);
const locationContainerMatch = source.match(
/<span className="([^"]+)">\s*\{location\}\s*<\/span>/,
);
assert.ok(
locationContainerMatch !== null &&
/(?:^|\s)(truncate|max-w-full|break-words)(?:\s|$)/.test(
locationContainerMatch[1],
),
"Location should use overflow-safe text classes in compact card.",
);
const emailSpanMatch = source.match(
/<span className="([^"]+)">\s*\{lead\.email \|\| "Keine E-Mail"\}\s*<\/span>/,
);
assert.ok(
emailSpanMatch !== null &&
/(?:^|\s)(break-all|max-w-full|min-w-0)(?:\s|$)/.test(
emailSpanMatch[1],
),
"Lead email should use overflow-safe text classes in compact card.",
);
const phoneSpanMatch = source.match(
/<span className="([^"]+)">\s*\{lead\.phone\}\s*<\/span>/,
);
assert.ok(
phoneSpanMatch !== null &&
/(?:^|\s)(break-all|max-w-full|min-w-0)(?:\s|$)/.test(phoneSpanMatch[1]),
"Lead phone should use overflow-safe text classes in compact card.",
);
assert.match(source, /Kontaktstatus/);
assert.match(source, /Review-E-Mail/);
assert.match(source, /Review-Quelle/);
assert.match(source, /Ansprechperson/);
assert.match(source, /Genannte E-Mail als Business-Kontakt/);
assert.match(source, /Duplikatstatus/);
assert.match(source, /Sperrstatus/);
assert.match(source, /Sperren/);
assert.match(source, /Speichern/);
});