/** * ListingTemplateModal Component * * Detailed modal view for listing template visualization with paginated tables. * * Features: * - Display all listing template metadata * - Paginated slots table (default 10 items/page) * - Paginated actions table (default 10 items/page) * - Structure visualization * - Selector truncation with copy functionality * * @component * @layer Presentation */ import { useState, useMemo } from 'react'; import { Check, X } from 'lucide-react'; import type { ListingTemplate, DataRole, ActionType } from '@archer/domain'; import { Modal } from '@/components/shared'; import { Pagination } from '@/components/ui/Pagination'; import { ErrorBoundary } from '@/components/ErrorBoundary'; import { formatListingType, formatDataRole, formatActionType } from '@/lib/pattern-format-utils'; interface ListingTemplateModalProps { /** Selected listing template to display */ template: ListingTemplate | null; /** Whether modal is open */ isOpen: boolean; /** Close handler */ onClose: () => void; } /** * Badge component for data role display */ function RoleBadge({ role }: { role: DataRole }) { return ( {formatDataRole(role)} ); } /** * Badge component for action type display */ function ActionBadge({ type }: { type: ActionType }) { return ( {formatActionType(type)} ); } /** * ListingTemplateModal Component */ export function ListingTemplateModal({ template, isOpen, onClose }: ListingTemplateModalProps) { // Pagination state for slots table const [slotsPage, setSlotsPage] = useState(1); const [slotsPageSize, setSlotsPageSize] = useState(10); // Pagination state for actions table const [actionsPage, setActionsPage] = useState(1); const [actionsPageSize, setActionsPageSize] = useState(10); // Convert slots Record to array for pagination const slotsArray = useMemo(() => { if (!template?.slots) return []; return Object.entries(template.slots).map(([key, slot]) => ({ key, ...slot, })); }, [template?.slots]); // Paginate slots const paginatedSlots = useMemo(() => { const start = (slotsPage - 1) * slotsPageSize; const end = start + slotsPageSize; return slotsArray.slice(start, end); }, [slotsArray, slotsPage, slotsPageSize]); // Paginate actions const paginatedActions = useMemo(() => { if (!template?.itemActions) return []; const start = (actionsPage - 1) * actionsPageSize; const end = start + actionsPageSize; return template.itemActions.slice(start, end); }, [template?.itemActions, actionsPage, actionsPageSize]); if (!isOpen || !template) return null; const totalActions = template.itemActions?.length ?? 0; return (
Unable to display template details
} > {/* Slots Table */}

Data Slots ({slotsArray.length})

{slotsArray.length === 0 ? (

No data slots defined

) : ( <>
{paginatedSlots.map((slot, idx) => ( ))}
Role Selector Extract Parse Required
{slot.selector} {slot.extract || 'text'} {slot.parse || 'none'} {slot.required ? ( ) : ( )}
{/* Slots Pagination */} {slotsArray.length > 10 && (
{ setSlotsPageSize(size); setSlotsPage(1); }} />
)} )}
{/* Actions Table */}

Item Actions ({totalActions})

{totalActions === 0 ? (

No item actions defined

) : ( <>
{paginatedActions.map((action, idx) => ( ))}
Action Type Selector Trigger
{action.selector} {action.trigger || 'click'}
{/* Actions Pagination */} {totalActions > 10 && (
{ setActionsPageSize(size); setActionsPage(1); }} />
)} )}
{/* Structure Info */} {template.structure && (

Structure

Layout: {template.structure.layout}
{template.structure.itemsPerRow && (
Items Per Row: {template.structure.itemsPerRow}
)} {template.structure.direction && (
Direction: {template.structure.direction}
)} {template.structure.lazyLoaded !== undefined && (
Lazy Loaded: {template.structure.lazyLoaded ? 'Yes' : 'No'}
)} {template.structure.virtualized !== undefined && (
Virtualized: {template.structure.virtualized ? 'Yes' : 'No'}
)}
)} {/* Item Selector Details */}

Item Selector

Primary Selector: {template.itemSelector.primary}
{template.itemSelector.fallbacks && template.itemSelector.fallbacks.length > 0 && (
Fallback Selectors ({template.itemSelector.fallbacks.length}):
{template.itemSelector.fallbacks.map((fallback, idx) => ( {fallback} ))}
)}
); }