(null);
const [page, setPage] = useState(1);
const queryKey = useMemo(
- () => ["admin-submissions", statusFilter, page],
- [statusFilter, page],
+ () => ["admin-submissions", statusFilter, countryFilter, page],
+ [statusFilter, countryFilter, page],
);
const { data, isLoading, error } = useQuery({
@@ -74,6 +77,7 @@ export default function AdminSubmissionsPage() {
p_status: statusFilter,
p_page: page,
p_page_size: 20,
+ p_country: countryFilter,
},
);
if (!result.ok) throw new Error(result.error.message);
@@ -260,6 +264,29 @@ export default function AdminSubmissionsPage() {
))}
+ {/* Country filter */}
+
+
+ {t("admin.countryLabel")}
+
+
+
+
{/* Loading */}
{isLoading && }
@@ -404,6 +431,12 @@ function AdminSubmissionCard({
+
{submission.user_trust_score !== null &&
submission.user_trust_score !== undefined && (
0
+ THEN round(100.0 * uts.approved_submissions / uts.total_submissions)
+ ELSE NULL
+ END,
+ 'user_flagged', (uts.flagged_at IS NOT NULL),
+ 'review_notes', ps.review_notes,
+ 'existing_product_match', (
+ SELECT jsonb_build_object(
+ 'product_id', p.product_id,
+ 'product_name', p.product_name
+ )
+ FROM products p
+ WHERE p.ean = ps.ean AND p.is_deprecated IS NOT TRUE
+ LIMIT 1
+ )
+ ) AS row_obj
+ FROM public.product_submissions ps
+ LEFT JOIN public.user_trust_scores uts ON uts.user_id = ps.user_id
+ WHERE (p_status = 'all' OR ps.status = p_status)
+ AND (p_country IS NULL
+ OR ps.scan_country = p_country
+ OR ps.suggested_country = p_country)
+ ORDER BY ps.created_at ASC
+ OFFSET v_offset
+ LIMIT LEAST(p_page_size, 50)
+ ) sub;
+
+ RETURN jsonb_build_object(
+ 'api_version', '1.0',
+ 'total', v_total,
+ 'page', GREATEST(p_page, 1),
+ 'pages', GREATEST(CEIL(v_total::numeric / LEAST(p_page_size, 50)), 1),
+ 'page_size', LEAST(p_page_size, 50),
+ 'status_filter', p_status,
+ 'country_filter', p_country,
+ 'submissions', v_items
+ );
+END;
+$$;
+
+-- Updated grants for new signature (4 params)
+GRANT EXECUTE ON FUNCTION public.api_admin_get_submissions(text, integer, integer, text)
+ TO service_role, authenticated;
+
+COMMENT ON FUNCTION public.api_admin_get_submissions IS
+ 'Purpose: List product submissions with trust enrichment and country context
+ Auth: authenticated (SECURITY DEFINER)
+ Params: p_status (default pending), p_page (default 1), p_page_size (default 20, max 50), p_country (optional country filter)
+ Returns: JSONB {api_version, total, page, pages, page_size, status_filter, country_filter, submissions: [...]}
+ Country filter: matches scan_country OR suggested_country
+ Backward compatible: new p_country param defaults to NULL (no filter)';
diff --git a/supabase/tests/scanner_functions.test.sql b/supabase/tests/scanner_functions.test.sql
index 148e4e99..6bc4a773 100644
--- a/supabase/tests/scanner_functions.test.sql
+++ b/supabase/tests/scanner_functions.test.sql
@@ -7,7 +7,7 @@
-- ─────────────────────────────────────────────────────────────────────────────
BEGIN;
-SELECT plan(70);
+SELECT plan(72);
-- ─── Fixtures ───────────────────────────────────────────────────────────────
@@ -457,6 +457,18 @@ SELECT lives_ok(
'api_admin_get_submissions lives_ok with trust enrichment'
);
+-- api_admin_get_submissions response envelope contains country_filter key (#925)
+SELECT ok(
+ public.api_admin_get_submissions('all', 1, 5) ? 'country_filter',
+ 'api_admin_get_submissions response has country_filter key'
+);
+
+-- api_admin_get_submissions accepts p_country filter (#925)
+SELECT lives_ok(
+ $$SELECT public.api_admin_get_submissions('all', 1, 5, 'PL')$$,
+ 'api_admin_get_submissions lives_ok with country filter'
+);
+
-- api_admin_submission_velocity returns expected keys
SELECT ok(
public.api_admin_submission_velocity() ?& ARRAY['api_version', 'last_24h', 'last_7d', 'pending_count', 'status_breakdown', 'top_submitters'],