Unexpected token

TablePageTemplate.tsx:
This commit is contained in:
alorig
2025-11-09 19:47:04 +05:00
parent 76ec58da0b
commit 6b24f8f1e8

View File

@@ -28,12 +28,10 @@ import Input from '../components/form/input/InputField';
import SelectDropdown from '../components/form/SelectDropdown';
import { Dropdown } from '../components/ui/dropdown/Dropdown';
import { DropdownItem } from '../components/ui/dropdown/DropdownItem';
import Alert from '../components/ui/alert/Alert';
import AlertModal from '../components/ui/alert/AlertModal';
import { ChevronDownIcon, MoreDotIcon, PlusIcon } from '../icons';
import { useHeaderMetrics } from '../context/HeaderMetricsContext';
import { useToast } from '../components/ui/toast/ToastContainer';
import { pageNotifications } from '../config/pages/notifications.config';
import { getDeleteModalConfig } from '../config/pages/delete-modal.config';
import { getBulkActionModalConfig } from '../config/pages/bulk-action-modal.config';
import { getTableActionsConfig } from '../config/pages/table-actions.config';
@@ -102,7 +100,7 @@ interface TablePageTemplateProps {
onCreate?: () => void;
createLabel?: string;
onCreateIcon?: ReactNode; // Icon for create button
onExport?: () => void;
onExportCSV?: () => void; // CSV export button handler
onExportIcon?: ReactNode; // Icon for export button
onImport?: () => void;
onImportIcon?: ReactNode; // Icon for import button
@@ -146,7 +144,7 @@ export default function TablePageTemplate({
subtitle,
columns,
data,
loading = false,
loading: _loading = false, // Unused - component uses showContent for loading state
showContent = true,
filters = [],
filterValues = {},
@@ -157,7 +155,7 @@ export default function TablePageTemplate({
onCreate,
createLabel = '+ Add',
onCreateIcon,
onExport,
onExportCSV,
onExportIcon,
onImport,
onImportIcon,
@@ -172,17 +170,17 @@ export default function TablePageTemplate({
onBulkUpdateStatus,
onBulkAction,
onRowAction,
onExport,
getItemDisplayName = (row: any) => row.name || row.keyword || row.title || String(row.id),
className = '',
}: TablePageTemplateProps) {
const location = useLocation();
const [isBulkActionsDropdownOpen, setIsBulkActionsDropdownOpen] = useState(false);
const [openRowActions, setOpenRowActions] = useState<Map<string | number, boolean>>(new Map());
const rowActionButtonRefs = React.useRef<Map<string | number, React.RefObject<HTMLButtonElement>>>(new Map());
const rowActionButtonRefs = React.useRef<Map<string | number, React.RefObject<HTMLButtonElement | null>>>(new Map());
const bulkActionsButtonRef = React.useRef<HTMLButtonElement>(null);
// Get notification config for current page
const notificationConfig = pageNotifications[location.pathname];
const deleteModalConfig = getDeleteModalConfig(location.pathname);
const bulkActionModalConfig = getBulkActionModalConfig(location.pathname);
const tableActionsConfig = getTableActionsConfig(location.pathname);
@@ -303,7 +301,7 @@ export default function TablePageTemplate({
} else if (actionKey === 'delete' && onDelete && deleteModalConfig) {
handleDeleteClick(row);
} else if (actionKey === 'export' && onExport) {
onExport(row);
await onExport(row);
} else if (onRowAction) {
// For custom row actions, use onRowAction with full row object
onRowAction(actionKey, row).catch((error: any) => {
@@ -649,7 +647,7 @@ export default function TablePageTemplate({
<Dropdown
isOpen={isBulkActionsDropdownOpen && selectedIds.length > 0}
onClose={() => setIsBulkActionsDropdownOpen(false)}
anchorRef={bulkActionsButtonRef}
anchorRef={bulkActionsButtonRef as React.RefObject<HTMLElement>}
placement="bottom-left"
className="w-48 p-2"
>
@@ -683,12 +681,12 @@ export default function TablePageTemplate({
{/* Action Buttons - Right aligned */}
<div className="flex gap-2">
{onExport && (
{onExportCSV && (
<Button
variant="secondary"
size="md"
endIcon={onExportIcon}
onClick={onExport}
onClick={onExportCSV}
>
Export CSV
</Button>
@@ -747,16 +745,24 @@ export default function TablePageTemplate({
key={column.key}
isHeader
className={`px-5 py-3 font-medium text-gray-500 text-${column.align || 'start'} text-theme-xs dark:text-gray-400 ${column.sortable ? 'cursor-pointer hover:text-gray-700 dark:hover:text-gray-300' : ''} ${isLastColumn && rowActions.length > 0 ? 'pr-16' : ''}`}
onClick={() => column.sortable && handleSort(column)}
>
{column.sortable ? (
<div onClick={() => handleSort(column)} className="flex items-center">
{column.label}
{getSortIcon(column)}
</div>
) : (
<>
{column.label}
{getSortIcon(column)}
</>
)}
</TableCell>
);
})}
</TableRow>
</TableHeader>
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05] igny8-table-body" style={{ position: 'relative' }}>
<TableBody className="divide-y divide-gray-100 dark:divide-white/[0.05] igny8-table-body">
{!showContent ? (
// Loading Skeleton Rows - Always show 10 rows to match page size
Array.from({ length: 10 }).map((_, index) => (
@@ -769,7 +775,6 @@ export default function TablePageTemplate({
))
) : data.length === 0 ? null : (
data.map((row, index) => {
const rowId = row.id?.toString() || index.toString();
// Use consistent key for expandedRows (number or index)
const rowKey = row.id || index;
const isRowExpanded = expandedRows.has(rowKey);
@@ -811,7 +816,6 @@ export default function TablePageTemplate({
<React.Fragment key={row.id || index}>
<TableRow
className={`igny8-data-row ${isRowAdded ? 'bg-blue-50 dark:bg-blue-500/10' : ''}`}
style={{ animationDelay: `${index * 0.03}s` }}
>
{selection && (
<TableCell className="px-5 py-4 text-start">
@@ -830,7 +834,8 @@ export default function TablePageTemplate({
// Get or create ref for this row's actions button
if (isLastColumn && rowActions.length > 0) {
if (!rowActionButtonRefs.current.has(rowId)) {
rowActionButtonRefs.current.set(rowId, React.createRef<HTMLButtonElement>());
const ref = React.createRef<HTMLButtonElement>();
rowActionButtonRefs.current.set(rowId, ref);
}
}
@@ -838,7 +843,6 @@ export default function TablePageTemplate({
<TableCell
key={column.key}
className={`px-5 py-4 text-${column.align || 'start'} text-gray-800 dark:text-white/90 ${isLastColumn && rowActions.length > 0 ? 'relative pr-16' : ''}`}
style={isLastColumn && rowActions.length > 0 ? { position: 'relative', overflow: 'visible' } : undefined}
>
<div className={`flex items-center ${column.toggleable && hasToggleContent ? 'justify-between w-full' : ''} gap-2`}>
<div className="flex-1">
@@ -849,14 +853,15 @@ export default function TablePageTemplate({
)}
</div>
{column.toggleable && hasToggleContent && (
<div onClick={(e) => e.stopPropagation()}>
<ToggleButton
isExpanded={isRowExpanded}
onClick={(e) => {
e.stopPropagation();
onClick={() => {
handleToggle(!isRowExpanded, rowKey);
}}
hasContent={hasToggleContent}
/>
</div>
)}
</div>
@@ -901,7 +906,8 @@ export default function TablePageTemplate({
}
// Multiple actions - use dropdown
const buttonRef = rowActionButtonRefs.current.get(rowId)!;
const buttonRef = rowActionButtonRefs.current.get(rowId);
if (!buttonRef) return null;
const isOpen = openRowActions.get(rowId) || false;
return (
@@ -936,11 +942,11 @@ export default function TablePageTemplate({
return newMap;
});
}}
anchorRef={buttonRef}
anchorRef={buttonRef as React.RefObject<HTMLElement>}
placement="right"
className="w-48 p-2"
>
{rowActions.map((action, actionIndex) => {
{rowActions.map((action) => {
const isEdit = action.key === 'edit';
const isDelete = action.key === 'delete';
const isExport = action.key === 'export';
@@ -949,21 +955,21 @@ export default function TablePageTemplate({
const getIconWithColor = () => {
if (!action.icon) return null;
const iconElement = action.icon as React.ReactElement;
const existingClassName = iconElement.props?.className || "";
const existingClassName = (iconElement.props as any)?.className || "";
const baseSize = existingClassName.includes("w-") ? "" : "w-5 h-5 ";
if (isEdit) {
return React.cloneElement(iconElement, {
className: `${baseSize}text-blue-light-500 ${existingClassName}`.trim()
});
} as any);
} else if (isDelete) {
return React.cloneElement(iconElement, {
className: `${baseSize}text-error-500 ${existingClassName}`.trim()
});
} as any);
} else if (isExport) {
return React.cloneElement(iconElement, {
className: `${baseSize}text-gray-600 dark:text-gray-400 ${existingClassName}`.trim()
});
} as any);
}
return action.icon;
};
@@ -1090,276 +1096,6 @@ export default function TablePageTemplate({
</div>
)}
{/* AI Request Logs Section - DEPRECATED: Now using backend console logging */}
{/* <AIRequestLogsSection /> */}
</div>
);
}
// AI Request Logs Component - DEPRECATED: Now using backend console logging
// All AI logging is now handled by backend console logging (ConsoleStepTracker)
/*
function AIRequestLogsSection() {
const { logs, clearLogs } = useAIRequestLogsStore();
const [isExpanded, setIsExpanded] = useState(false);
if (logs.length === 0) {
return null;
}
const formatTime = (date: Date) => {
return new Intl.DateTimeFormat('en-US', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false,
}).format(date);
};
const getStatusColor = (status: string) => {
switch (status) {
case 'success':
return 'text-green-600 dark:text-green-400';
case 'error':
return 'text-red-600 dark:text-red-400';
case 'pending':
return 'text-yellow-600 dark:text-yellow-400';
default:
return 'text-gray-600 dark:text-gray-400';
}
};
const getStatusBadge = (status: string) => {
switch (status) {
case 'success':
return 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400';
case 'error':
return 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400';
case 'pending':
return 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400';
default:
return 'bg-gray-100 text-gray-800 dark:bg-gray-900/30 dark:text-gray-400';
}
};
return (
<div className="mt-6 border-t border-gray-200 dark:border-gray-800 pt-6">
{isExpanded && (
<div className="flex justify-center">
<div className="w-1/2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800/50">
{/* Header inside panel */}
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
AI Debug Logs
</h3>
<span className="text-xs px-2 py-1 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400">
{logs.length}
</span>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsExpanded(!isExpanded)}
className="text-xs"
>
{isExpanded ? 'Collapse' : 'Expand'}
</Button>
<Button
variant="outline"
size="sm"
onClick={clearLogs}
className="text-xs text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20"
>
Clear
</Button>
</div>
</div>
{/* Scrollable content */}
<div className="p-4 max-h-96 overflow-y-auto">
<div className="space-y-4">
{logs.map((log) => (
<React.Fragment key={log.id}>
<div
className="p-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900"
>
{/* Header */}
<div className="flex items-start justify-between mb-3">
<div className="flex items-center gap-2">
<span className="text-xs font-semibold text-gray-700 dark:text-gray-300 font-mono-custom">
{log.function}
</span>
<span className={`text-xs px-2 py-0.5 rounded ${getStatusBadge(log.status)}`}>
{log.status.toUpperCase()}
</span>
{log.duration && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{log.duration}ms
</span>
)}
</div>
<span className="text-xs text-gray-500 dark:text-gray-400">
{formatTime(log.timestamp)}
</span>
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mb-3 font-mono-custom">
{log.endpoint}
</div>
{/* Request/Response Details (Collapsible) */}
<details className="mt-3">
<summary className="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-300">
Request/Response Details
</summary>
<div className="mt-2 space-y-2">
<details className="text-xs">
<summary className="text-gray-500 dark:text-gray-400 cursor-pointer">Request</summary>
<div className="mt-1 p-2 bg-gray-50 dark:bg-gray-800 rounded text-xs overflow-x-auto font-mono-custom">
<pre className="whitespace-pre-wrap">
{JSON.stringify(log.request.body || log.request.params || {}, null, 2)}
</pre>
</div>
</details>
{log.response && (
<details className="text-xs">
<summary className="text-gray-500 dark:text-gray-400 cursor-pointer">
Response ({log.response.status})
{log.response.errorType && (
<span className="ml-2 px-1.5 py-0.5 rounded bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400 text-[10px] font-semibold">
{log.response.errorType}
</span>
)}
</summary>
<div className="mt-1 p-2 bg-gray-50 dark:bg-gray-800 rounded text-xs overflow-x-auto font-mono-custom">
{log.response.error ? (
<div className="text-red-600 dark:text-red-400">
{log.response.error}
</div>
) : log.response.data ? (
<pre className="whitespace-pre-wrap">
{JSON.stringify(log.response.data, null, 2)}
</pre>
) : null}
</div>
</details>
)}
</div>
</details>
</div>
{/* AI Debug Steps */}
<div className="border border-gray-200 dark:border-gray-700 rounded-lg p-4 bg-white dark:bg-gray-900">
<h4 className="text-xs font-semibold text-gray-700 dark:text-gray-300 mb-3">
AI Debug Steps ({log.requestSteps.length + log.responseSteps.length} total steps)
</h4>
<div className="space-y-3">
{(() => {
// Combine and sort all steps by stepNumber
const allSteps = [
...log.requestSteps.map((step, idx) => ({ ...step, type: 'request', originalIdx: idx })),
...log.responseSteps.map((step, idx) => ({ ...step, type: 'response', originalIdx: idx }))
].sort((a, b) => (a.stepNumber || 0) - (b.stepNumber || 0));
return allSteps.length > 0 ? (
allSteps.map((step, idx) => (
<div
key={`${step.type}-${step.originalIdx}-${idx}`}
className="p-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50"
>
<div className="flex items-center gap-2 mb-2">
<span className="text-xs font-mono text-gray-500 dark:text-gray-400">
{step.stepNumber}.
</span>
<span className={`text-xs px-2 py-0.5 rounded ${getStatusBadge(step.status)}`}>
{step.status.toUpperCase()}
</span>
<span className="text-xs text-gray-400 dark:text-gray-500">
{step.type === 'request' ? 'Request' : 'Response'}
</span>
{step.duration && (
<span className="text-xs text-gray-500 dark:text-gray-400">
{step.duration}ms
</span>
)}
</div>
<div className="text-xs font-semibold text-gray-700 dark:text-gray-300 mb-1">
{step.stepName}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 font-mono mb-1">
{step.functionName}
</div>
{step.message && (
<div className="text-xs text-gray-500 dark:text-gray-400 mt-2">
{step.message}
</div>
)}
{step.error && (
<div className="text-xs text-red-600 dark:text-red-400 mt-2">
Error: {step.error}
</div>
)}
</div>
))
) : (
<div className="text-xs text-gray-500 dark:text-gray-400 italic">
No steps logged yet
</div>
);
})()}
</div>
</div>
</React.Fragment>
))}
</div>
</div>
</div>
</div>
)}
{!isExpanded && logs.length > 0 && (
<div className="flex justify-center">
<div className="w-1/2 border border-gray-200 dark:border-gray-700 rounded-lg bg-gray-50 dark:bg-gray-800/50">
{/* Header when collapsed */}
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center gap-3">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300">
AI Debug Logs
</h3>
<span className="text-xs px-2 py-1 rounded-full bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400">
{logs.length}
</span>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => setIsExpanded(!isExpanded)}
className="text-xs"
>
{isExpanded ? 'Collapse' : 'Expand'}
</Button>
<Button
variant="outline"
size="sm"
onClick={clearLogs}
className="text-xs text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20"
>
Clear
</Button>
</div>
</div>
<div className="p-4">
<div className="text-xs text-gray-500 dark:text-gray-400">
Click "Expand" to view {logs.length} AI request log{logs.length !== 1 ? 's' : ''}
</div>
</div>
</div>
</div>
)}
</div>
);
}
*/