diff --git a/frontend/messages/de.json b/frontend/messages/de.json
index 049e265f..c67f6d36 100644
--- a/frontend/messages/de.json
+++ b/frontend/messages/de.json
@@ -1096,7 +1096,10 @@
"allCountries": "Alle Länder",
"gs1Mismatch": "GS1-Barcode deutet auf {gs1Country}, aber Einreichung zielt auf {effectiveCountry}",
"regionMismatch": "Gescannt in {scanCountry}, aber vorgeschlagen für {suggestedCountry}",
- "crossCountryProducts": "Gleiche EAN existiert in {countries} ({count} Produkt(e))"
+ "crossCountryProducts": "Gleiche EAN existiert in {countries} ({count} Produkt(e))",
+ "noCountry": "Kein Land",
+ "noCountryHint": "Eingereicht vor der Länder-Erfassung",
+ "gs1InfoHint": "GS1-Barcode registriert in {gs1Country} (informativ)"
},
"monitoring": {
"title": "Systemüberwachung",
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index fce59017..1944a0d3 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -1096,7 +1096,10 @@
"allCountries": "All countries",
"gs1Mismatch": "GS1 barcode suggests {gs1Country} but submission targets {effectiveCountry}",
"regionMismatch": "Scanned in {scanCountry} but suggested for {suggestedCountry}",
- "crossCountryProducts": "Same EAN exists in {countries} ({count} product(s))"
+ "crossCountryProducts": "Same EAN exists in {countries} ({count} product(s))",
+ "noCountry": "No country",
+ "noCountryHint": "Submitted before country tracking was added",
+ "gs1InfoHint": "GS1 barcode registered in {gs1Country} (informational)"
},
"monitoring": {
"title": "System Monitoring",
diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json
index 0b944438..5fab5bdf 100644
--- a/frontend/messages/pl.json
+++ b/frontend/messages/pl.json
@@ -1096,7 +1096,10 @@
"allCountries": "Wszystkie kraje",
"gs1Mismatch": "Kod GS1 wskazuje na {gs1Country}, ale zgłoszenie dotyczy {effectiveCountry}",
"regionMismatch": "Zeskanowano w {scanCountry}, ale zasugerowano dla {suggestedCountry}",
- "crossCountryProducts": "Ten sam EAN istnieje w {countries} ({count} produkt(ów))"
+ "crossCountryProducts": "Ten sam EAN istnieje w {countries} ({count} produkt(ów))",
+ "noCountry": "Brak kraju",
+ "noCountryHint": "Zgłoszone przed dodaniem śledzenia krajów",
+ "gs1InfoHint": "Kod GS1 zarejestrowany w {gs1Country} (informacyjnie)"
},
"monitoring": {
"title": "Monitoring systemu",
diff --git a/frontend/src/app/app/admin/submissions/page.test.tsx b/frontend/src/app/app/admin/submissions/page.test.tsx
index 75eae21a..19a10d0a 100644
--- a/frontend/src/app/app/admin/submissions/page.test.tsx
+++ b/frontend/src/app/app/admin/submissions/page.test.tsx
@@ -26,8 +26,12 @@ vi.mock("@/components/common/skeletons", () => ({
}));
vi.mock("@/components/common/CountryChip", () => ({
- CountryChip: ({ country }: { country: string | null }) =>
- country ? {country} : null,
+ CountryChip: ({ country, nullLabel }: { country: string | null; nullLabel?: string }) =>
+ country ? (
+ {country}
+ ) : nullLabel ? (
+ {nullLabel}
+ ) : null,
}));
// ─── Helpers ────────────────────────────────────────────────────────────────
@@ -581,7 +585,7 @@ describe("AdminSubmissionsPage", () => {
expect(chip).toHaveTextContent("DE");
});
- it("does not render country chip when both countries are null", async () => {
+ it("shows fallback country chip when both countries are null", async () => {
mockCallRpc.mockImplementation((_client: unknown, fnName: string) => {
if (fnName === "api_admin_get_submissions") {
return Promise.resolve({
@@ -606,7 +610,9 @@ describe("AdminSubmissionsPage", () => {
await waitFor(() => {
expect(screen.getByText("Unknown Origin")).toBeInTheDocument();
});
- expect(screen.queryByTestId("country-chip")).not.toBeInTheDocument();
+ const chip = screen.getByTestId("country-chip");
+ expect(chip).toBeInTheDocument();
+ expect(chip).toHaveAttribute("data-null-country");
});
it("renders country filter dropdown", async () => {
@@ -857,4 +863,119 @@ describe("AdminSubmissionsPage", () => {
});
expect(screen.queryByTestId("cross-country-badge")).not.toBeInTheDocument();
});
+
+ // ─── Legacy Null-Country UX Tests ─────────────────────────────────────────
+
+ it("shows legacy help text for null-country submission", async () => {
+ mockCallRpc.mockImplementation((_client: unknown, fnName: string) => {
+ if (fnName === "api_admin_get_submissions") {
+ return Promise.resolve({
+ ok: true,
+ data: {
+ submissions: [
+ makeSubmission({
+ scan_country: null,
+ suggested_country: null,
+ product_name: "Legacy Product",
+ }),
+ ],
+ page: 1,
+ pages: 1,
+ total: 1,
+ },
+ });
+ }
+ return Promise.resolve({ ok: true, data: {} });
+ });
+ render(
+ ℹ {t("admin.noCountryHint")} +
+ )} + {submission.existing_product_match && (⚠ Possible duplicate — existing product #{submission.existing_product_match.product_id}{" "} @@ -470,18 +478,26 @@ function AdminSubmissionCard({ {submission.gs1_hint && submission.gs1_hint.code !== "UNKNOWN" && submission.gs1_hint.code !== "STORE" && - (submission.suggested_country ?? submission.scan_country) && - submission.gs1_hint.code !== - (submission.suggested_country ?? submission.scan_country) && ( + effectiveCountry && + submission.gs1_hint.code !== effectiveCountry && (
⚠ {t("admin.gs1Mismatch", { gs1Country: submission.gs1_hint.name, - effectiveCountry: - (submission.suggested_country ?? submission.scan_country)!, + effectiveCountry: effectiveCountry, })}
)} + {/* GS1 informational hint for legacy null-country submissions */} + {!effectiveCountry && + submission.gs1_hint && + submission.gs1_hint.code !== "UNKNOWN" && + submission.gs1_hint.code !== "STORE" && ( ++ ℹ {t("admin.gs1InfoHint", { gs1Country: submission.gs1_hint.name })} +
+ )} + {/* Region mismatch badge (#929) */} {submission.scan_country && submission.suggested_country && diff --git a/frontend/src/components/common/CountryChip.test.tsx b/frontend/src/components/common/CountryChip.test.tsx index 9e940366..85935006 100644 --- a/frontend/src/components/common/CountryChip.test.tsx +++ b/frontend/src/components/common/CountryChip.test.tsx @@ -1,5 +1,5 @@ -import { describe, it, expect, vi } from "vitest"; import { render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; import { CountryChip } from "./CountryChip"; // ─── Mocks ──────────────────────────────────────────────────────────────────── @@ -24,6 +24,14 @@ describe("CountryChip", () => { expect(container.innerHTML).toBe(""); }); + it("renders fallback chip with nullLabel when country is null", () => { + render(