import React, { CSSProperties, FC, useCallback, useMemo, useState, } from 'react'; import { motion } from 'framer-motion'; import { HiOutlineHashtag, HiOutlineCurrencyDollar, HiOutlineUser, HiOutlineClock, HiOutlineDocumentText, HiOutlineTrash, HiOutlinePencil, HiOutlineClipboard, HiOutlineCheck, } from 'react-icons/hi2'; import { HiOutlineExternalLink, HiOutlineX } from 'react-icons/hi'; import type { EntityAssociation } from '../../../main/db/schema'; import { EntityFormat } from '@hashgraphonline/conversational-agent'; import Typography from '../ui/Typography'; import { cn } from '../../lib/utils'; import { useConfigStore } from '../../stores/configStore'; import { formatDistanceToNow, format } from 'date-fns'; /** * Props for EntityCard component */ export interface EntityCardProps { id?: string; entity: EntityAssociation; isSelected: boolean; onSelect: (selected: boolean) => void; onDelete: () => void; onRename: (newName: string) => void; onCopyToClipboard: () => void; onKeyDown?: (event: React.KeyboardEvent) => void; tabIndex?: number; style?: CSSProperties; } interface EntityIconProps { entityType: string; } const EntityIcon: FC = ({ entityType }) => { switch (entityType) { case 'topicId': return ( ); case 'tokenId': return ( ); case 'accountId': return ( ); case 'scheduleId': return ( ); case 'contractId': return ( ); default: return ( ); } }; /** * Individual entity card component with actions */ export const EntityCard: FC = ({ id, entity, isSelected, onSelect, onDelete, onRename, onCopyToClipboard, onKeyDown, tabIndex, style, }) => { const { config } = useConfigStore(); const [isEditing, setIsEditing] = useState(false); const [editName, setEditName] = useState(entity.entityName); const [isDeleting, setIsDeleting] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [copied, setCopied] = useState(false); const [nameError, setNameError] = useState(null); const handleRename = useCallback(async () => { if (editName.trim() === '') { setNameError('Name cannot be empty'); return; } if (editName.trim() === entity.entityName) { setIsEditing(false); setNameError(null); return; } try { await onRename(editName.trim()); setIsEditing(false); setNameError(null); } catch { setNameError('Failed to rename entity'); } }, [editName, entity.entityName, onRename]); const handleDelete = useCallback(async () => { setIsDeleting(true); try { await onDelete(); setShowDeleteConfirm(false); } catch { setIsDeleting(false); } }, [onDelete]); const handleCopy = useCallback(() => { onCopyToClipboard(); setCopied(true); setTimeout(() => setCopied(false), 2000); }, [onCopyToClipboard]); const handleCancelEdit = useCallback(() => { setIsEditing(false); setEditName(entity.entityName); setNameError(null); }, [entity.entityName]); const hashScanUrl = useMemo(() => { const network = config?.hedera?.network || 'testnet'; const baseUrl = `https://hashscan.io/${network}`; switch (entity.entityType) { case 'topicId': return `${baseUrl}/topic/${entity.entityId}`; case 'tokenId': return `${baseUrl}/token/${entity.entityId}`; case 'accountId': return `${baseUrl}/account/${entity.entityId}`; case 'scheduleId': return `${baseUrl}/schedule/${entity.entityId}`; case 'contract': return `${baseUrl}/contract/${entity.entityId}`; default: return `${baseUrl}/entity/${entity.entityId}`; } }, [entity.entityId, entity.entityType, config?.hedera?.network]); const formatDate = useCallback((date: Date | string) => { const d = new Date(date); const now = new Date(); const diffMs = now.getTime() - d.getTime(); const diffDays = Math.floor(diffMs / 86400000); if (diffDays < 7) { return formatDistanceToNow(d, { addSuffix: true }); } return format(d, 'MMM d, yyyy'); }, []); const parseMetadata = useCallback(() => { if (!entity.metadata) return null; try { return JSON.parse(entity.metadata); } catch { return null; } }, [entity.metadata]); const metadata = parseMetadata(); return ( {/* Selection overlay */} {isSelected && (
)}
{/* Header */}
onSelect(e.target.checked)} className='w-4 h-4 text-hgo-blue bg-gray-100 border-gray-300 rounded focus:ring-hgo-blue focus:ring-2' aria-label={`Select ${entity.entityName}`} />
{isEditing ? (
setEditName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleRename(); if (e.key === 'Escape') handleCancelEdit(); }} className='w-full px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-700 focus:ring-2 focus:ring-hgo-blue focus:outline-none' autoFocus /> {nameError && ( {nameError} )}
) : (
{entity.entityName}
{entity.entityType} {!entity.isActive && ( Inactive )}
)}
{!isEditing && (
)}
{/* Entity details */}
{entity.entityId}
{entity.transactionId && (
Transaction: {entity.transactionId}
)} {metadata && entity.entityType === 'tokenId' && (
{metadata.symbol && ( {metadata.symbol} )} {metadata.decimals && {metadata.decimals} decimals} {metadata.totalSupply && ( {parseInt(metadata.totalSupply).toLocaleString()} total supply )}
)}
{formatDate(entity.createdAt)} {entity.sessionId && ( {entity.sessionId || 'No session'} )}
{showDeleteConfirm && (
Delete Entity Are you sure you want to delete this entity? This action cannot be undone.
)} {/* Loading overlay */} {isDeleting && !showDeleteConfirm && (
)} ); };