import React, { useMemo, useState } from "react"; import { createPortal } from "react-dom"; import { Table as UITable, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../ui/table"; import { Button } from "../ui/button"; import { MoreVertical, ChevronRight, ChevronDown, Copy, Check, } from "lucide-react"; import { __ } from "../../lib/i18n"; import { ConditionalRender } from "../ui/conditional-render"; import { ErrorRequestInfo } from "../../lib/errors"; interface TableColumn { key: string; label: string; sortable?: boolean; visible?: boolean; width?: string; render?: (item: any, index: number) => React.ReactNode; } interface TableAction { key: string; label: string; icon: React.ReactNode; onClick: (item: any) => void; condition?: (item: any) => boolean; variant?: "default" | "destructive" | "outline"; } interface TableProps { data: any[]; columns: TableColumn[]; actions?: TableAction[]; isLoading?: boolean; isError?: boolean; errorText?: string; emptyText?: string; emptyDescription?: string; onCreateClick?: () => void; onSort?: (field: string) => void; getSortIcon?: (field: string) => React.ReactNode; selectedItemIds?: (string | number)[]; onSelectItem?: (id: string | number, checked: boolean) => void; onSelectAll?: (checked: boolean) => void; isAllSelected?: boolean; getItemId?: (item: any) => string | number; getItemStatus?: (item: any) => string; statusFilter?: string; capability?: string; skeletonRows?: number; errorDescription?: string; onRetry?: () => void; errorDetails?: string; errorRequestInfo?: ErrorRequestInfo; // Hierarchical support isHierarchical?: boolean; expandedIds?: Set; onToggleExpand?: (id: string | number) => void; getChildren?: (item: any) => any[]; renderRowContent?: ( item: any, index: number, isChild?: boolean, ) => React.ReactNode[]; } export const Table: React.FC = ({ data, columns, actions = [], isLoading = false, isError = false, errorText = __("Error loading data", "yatra"), emptyText = __("No items found", "yatra"), emptyDescription = __("Get started by creating your first item.", "yatra"), onCreateClick, onSort, getSortIcon, selectedItemIds = [], onSelectItem, onSelectAll, isAllSelected = false, getItemId = (item) => item.id, getItemStatus = (item) => item.status, statusFilter = "", capability = "yatra_view_trips", skeletonRows = 5, errorDescription = __( "Something went wrong while loading this data. Please try again in a moment.", "yatra", ), onRetry, errorDetails, errorRequestInfo, // Hierarchical props isHierarchical = false, expandedIds = new Set(), onToggleExpand, getChildren, renderRowContent, }) => { const [openDropdownId, setOpenDropdownId] = useState( null, ); const [dropdownPosition, setDropdownPosition] = useState<{ top: number; right: number; } | null>(null); const [copied, setCopied] = useState(false); const handleRetry = () => { if (onRetry) { onRetry(); return; } if (typeof window !== "undefined") { window.location.reload(); } }; // Close dropdown when clicking outside React.useEffect(() => { const handleClickOutside = (event: MouseEvent) => { const target = event.target as HTMLElement; if ( target.closest("[data-dropdown-trigger]") || target.closest("[data-dropdown-content]") ) { return; } setOpenDropdownId(null); }; if (openDropdownId !== null) { document.addEventListener("click", handleClickOutside); return () => document.removeEventListener("click", handleClickOutside); } }, [openDropdownId]); const getVisibleActions = (item: any) => actions.filter((a) => !a.condition || a.condition(item)); const openActionsMenu = ( e: React.MouseEvent, item: any, ) => { e.preventDefault(); e.stopPropagation(); const itemId = getItemId(item); const rect = e.currentTarget.getBoundingClientRect(); const visibleActions = getVisibleActions(item); const dropdownHeight = visibleActions.length * 40 + 16; const spaceBelow = window.innerHeight - rect.bottom; const spaceAbove = rect.top; const shouldPositionAbove = spaceBelow < dropdownHeight && spaceAbove > dropdownHeight; // position:fixed uses viewport coordinates; getBoundingClientRect is viewport-relative setDropdownPosition({ top: shouldPositionAbove ? Math.max(8, rect.top - dropdownHeight) : rect.bottom, right: window.innerWidth - rect.right, }); setOpenDropdownId(openDropdownId === itemId ? null : itemId); }; const renderActionsDropdown = (item: any) => { const itemId = getItemId(item); if (openDropdownId !== itemId || !dropdownPosition) return null; return createPortal(
e.stopPropagation()} > {getVisibleActions(item).map((action) => ( ))}
, document.body, ); }; // Render skeleton loading state const renderSkeleton = () => ( {isHierarchical && } {onSelectItem && onSelectAll && ( )} {columns .filter((col) => col.visible !== false) .map((column) => ( {column.label} ))} {actions.length > 0 && ( {__("Actions", "yatra")} )} {[...Array(skeletonRows)].map((_, index) => ( {isHierarchical && (
)} {onSelectItem && onSelectAll && (
)} {columns .filter((col) => col.visible !== false) .map((column) => (
))} {actions.length > 0 && (
)} ))} ); // Render error state const composedErrorText = useMemo(() => { const parts: string[] = []; if (errorRequestInfo) { if (errorRequestInfo.method) { parts.push(`Request Method: ${errorRequestInfo.method}`); } if (errorRequestInfo.url) { parts.push(`Request URL: ${errorRequestInfo.url}`); } if (errorRequestInfo.payload) { parts.push(`Request Payload:\n${errorRequestInfo.payload}`); } } if (errorDetails) { parts.push(`Response:\n${errorDetails}`); } return parts.join("\n\n").trim(); }, [errorRequestInfo, errorDetails]); const renderError = () => (

{errorText}

{errorDescription}

{(errorDetails || errorRequestInfo) && (

{__("Technical details", "yatra")}

{errorRequestInfo && (
{errorRequestInfo.method && (
{__("Method:", "yatra")} {" "} {errorRequestInfo.method}
)} {errorRequestInfo.url && (
{__("URL:", "yatra")}{" "} {errorRequestInfo.url}
)} {errorRequestInfo.payload && (
{__("Payload:", "yatra")}
                      {errorRequestInfo.payload}
                    
)}
)} {errorDetails && (
                {errorDetails}
              
)}
)}
); // Render empty state const renderEmpty = () => (
{/* Background decoration */}
{/* Content */}
{/* Icon */}
{/* Text content */}

{emptyText}

{emptyDescription}

{/* Action button */} {onCreateClick && (
)}
); // Render hierarchical row with children const renderHierarchicalRow = ( item: any, index: number, isChild = false, ): React.ReactNode => { const itemId = getItemId(item); const itemStatus = getItemStatus(item); const isTrash = itemStatus === "trash" || statusFilter === "trash"; const children = getChildren ? getChildren(item) : []; const hasChildren = children.length > 0; const isExpanded = expandedIds.has(itemId); return ( {/* Expand/collapse column */} {hasChildren && onToggleExpand && ( )} {/* Selection checkbox */} {onSelectItem && ( onSelectItem(itemId, e.target.checked)} aria-label={__("Select item", "yatra")} /> )} {/* Custom row content or default columns */} {renderRowContent ? renderRowContent(item, index, isChild).map( (cellContent, cellIndex) => ( {cellContent} ), ) : columns .filter((col) => col.visible !== false) .map((column) => ( {column.render ? column.render(item, index) : item[column.key]} ))} {/* Actions */} {actions.length > 0 && (
{renderActionsDropdown(item)}
)}
{/* Render children if expanded */} {hasChildren && isExpanded && children.map((child, childIndex) => renderHierarchicalRow(child, childIndex, true), )}
); }; // Render flat row (original behavior) const renderFlatRow = (item: any, index: number): React.ReactNode => { const itemId = getItemId(item); const itemStatus = getItemStatus(item); const isTrash = itemStatus === "trash" || statusFilter === "trash"; return ( {onSelectItem && ( onSelectItem(itemId, e.target.checked)} aria-label={__("Select item", "yatra")} /> )} {columns .filter((col) => col.visible !== false) .map((column) => ( {column.render ? column.render(item, index) : item[column.key]} ))} {actions.length > 0 && (
{renderActionsDropdown(item)}
)}
); }; // Render table data const renderTable = () => { if (isLoading) return renderSkeleton(); if (isError) return renderError(); if (data.length === 0) return renderEmpty(); return ( {/* Expand/collapse column for hierarchical tables */} {isHierarchical && } {onSelectItem && onSelectAll && ( onSelectAll(e.target.checked)} aria-label={__("Select all items", "yatra")} /> )} {columns .filter((col) => col.visible !== false) .map((column) => ( {column.sortable && onSort ? ( ) : ( column.label )} ))} {actions.length > 0 && ( {__("Actions", "yatra")} )} {data.map((item, index) => isHierarchical ? renderHierarchicalRow(item, index) : renderFlatRow(item, index), )} ); }; return ( {renderTable()} ); };