import { Button, CheckboxControl, Icon, Spinner, ToggleControl } from '@wordpress/components'; import { useState } from '@wordpress/element'; import { code, external } from '@wordpress/icons'; import type { ProductRow, ProductStateMap, ProtectionStatus } from '../types'; import SnapshotModal from './SnapshotModal'; /** Base URL of the Mintall Keep web app, for deep-linking to product pages. */ const APP_URL = window.mintallKeep?.appUrl ?? ''; function getBarColor(score: number): string { if (score < 40) return '#d72c0d'; if (score < 80) return '#f5a623'; if (score >= 100) return '#00a32a'; return '#2c6ecb'; } function getTextColor(score: number): string { if (score < 40) return '#d72c0d'; if (score < 80) return '#9c6f19'; if (score >= 100) return '#00a32a'; return '#2c6ecb'; } interface ProductsTableProps { items: ProductRow[]; productState?: ProductStateMap; onVisibilityChange?: (productId: number, enabled: boolean) => void; onProtect?: (productId: number) => Promise; selectedIds?: Set; onSelectionChange?: (ids: Set) => void; } export default function ProductsTable({ items, productState, onVisibilityChange, onProtect, selectedIds, onSelectionChange, }: ProductsTableProps): JSX.Element { const [protectingIds, setProtectingIds] = useState>(new Set()); const [snapshotProduct, setSnapshotProduct] = useState<{ id: number; name: string } | null>(null); const showSelection = Boolean(selectedIds && onSelectionChange); const pageIds = items.map((item) => item.product_id); const selectedOnPage = pageIds.filter((id) => selectedIds?.has(id)); const allOnPageSelected = pageIds.length > 0 && selectedOnPage.length === pageIds.length; const someOnPageSelected = selectedOnPage.length > 0 && !allOnPageSelected; const toggleSelectAll = (checked: boolean): void => { if (!selectedIds || !onSelectionChange) return; const next = new Set(selectedIds); for (const id of pageIds) { if (checked) next.add(id); else next.delete(id); } onSelectionChange(next); }; const toggleSelectOne = (productId: number, checked: boolean): void => { if (!selectedIds || !onSelectionChange) return; const next = new Set(selectedIds); if (checked) next.add(productId); else next.delete(productId); onSelectionChange(next); }; const handleProtect = async (productId: number): Promise => { if (!onProtect) return; setProtectingIds((prev) => new Set(prev).add(productId)); try { await onProtect(productId); } finally { setProtectingIds((prev) => { const next = new Set(prev); next.delete(productId); return next; }); } }; // The Action column shows the single most relevant next step for a row, // mirroring the Shopify products table: Protect while unprotected, progress // while a job runs, Retry on failure, and a nudge toward the toggle once a // product is protected but still hidden from AI. const renderAction = ( item: ProductRow, visible: boolean, primaryIssue: string | null | undefined, ): JSX.Element | null => { const busy = protectingIds.has(item.product_id); switch (item.status as ProtectionStatus) { case 'IN_PROGRESS': return ( Protecting… ); case 'NEEDS_ATTENTION': return ( Failed {onProtect && ( )} ); case 'PROTECTED': if (!visible) return ← Use toggle; // Visible and nothing left to fix — otherwise the Readiness column // already surfaces the outstanding issue, so no action is needed. return primaryIssue ? null : Ready; default: return onProtect ? ( ) : null; } }; return ( <> {showSelection && ( )} {items.map((item) => { const state = productState?.[item.product_id]; const score = state?.readiness_score ?? 0; const primaryIssue = state?.primary_issue; const primaryIssueLabel = state?.primary_issue_label; const isProtected = item.status === 'PROTECTED'; const visible = state?.visible ?? false; return ( {showSelection && ( )} ); })}
Product Readiness AI visibility Action Details
toggleSelectOne(item.product_id, checked)} /> {item.product_image ? ( {item.product_name} ) : (
)} {item.product_url ? ( {item.product_name} ) : ( {item.product_name} )}
{score} {primaryIssue ? ( {primaryIssueLabel ?? primaryIssue} ) : null}
{onVisibilityChange ? ( onVisibilityChange(item.product_id, enabled)} __nextHasNoMarginBottom /> ) : visible ? ( 'On' ) : ( 'Off' )} {isProtected && (
{renderAction(item, visible, primaryIssue)} {APP_URL && state?.mintall_product_id ? ( ) : null}
{snapshotProduct && ( setSnapshotProduct(null)} /> )} ); } const progressStyle: React.CSSProperties = { display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13, color: '#757575', whiteSpace: 'nowrap', }; const hintStyle: React.CSSProperties = { fontSize: 13, color: '#757575', whiteSpace: 'nowrap', }; const readyStyle: React.CSSProperties = { fontSize: 13, fontWeight: 600, color: '#00a32a', whiteSpace: 'nowrap', }; const failedBadgeStyle: React.CSSProperties = { fontSize: 12, fontWeight: 600, color: '#d63638', background: '#fcf0f1', borderRadius: 4, padding: '2px 8px', whiteSpace: 'nowrap', };