| 468 | |
| 469 | // Main Containers component |
| 470 | const Containers = () => { |
| 471 | const [searchParams, setSearchParams] = useSearchParams(); |
| 472 | const [containers, setContainers] = useState([]); |
| 473 | const [isLoading, setIsLoading] = useState(true); |
| 474 | const [error, setError] = useState(null); |
| 475 | const [lastUpdated, setLastUpdated] = useState(null); |
| 476 | const [hasMore, setHasMore] = useState(true); |
| 477 | const [page, setPage] = useState(0); |
| 478 | const scrollContainerRef = useRef(null); |
| 479 | |
| 480 | // Filter and sort state initialized from URL parameters |
| 481 | const [filters, setFilters] = useState(() => ({ |
| 482 | original_filename: searchParams.get('filename') || '', |
| 483 | agent_id: searchParams.get('agent_id') || '', |
| 484 | source: searchParams.get('source') || '', |
| 485 | status: searchParams.get('status') || '', |
| 486 | container_id: searchParams.get('container_id') || '' |
| 487 | })); |
| 488 | const [sortBy, setSortBy] = useState(() => searchParams.get('sort_by') || 'submitted_at'); |
| 489 | const [sortOrder, setSortOrder] = useState(() => searchParams.get('sort_order') || 'desc'); |
| 490 | |
| 491 | const POLL_INTERVAL = 5000; // Poll every 5 seconds |
| 492 | const PAGE_SIZE = 20; |
| 493 | |
| 494 | |
| 495 | const fetchContainers = useCallback(async (pageNum = 0, reset = false) => { |
| 496 | try { |
| 497 | // Build filter variables instead of inline conditions |
| 498 | const variables = { |
| 499 | limit: PAGE_SIZE, |
| 500 | offset: pageNum * PAGE_SIZE |
| 501 | }; |
| 502 | |
| 503 | // Build where conditions as GraphQL variables |
| 504 | const whereConditions = []; |
| 505 | |
| 506 | if (filters.original_filename) { |
| 507 | whereConditions.push({ original_filename: { _ilike: `%${filters.original_filename}%` } }); |
| 508 | } |
| 509 | if (filters.agent_id) { |
| 510 | whereConditions.push({ agent_id: { _ilike: `%${filters.agent_id}%` } }); |
| 511 | } |
| 512 | if (filters.source) { |
| 513 | whereConditions.push({ source: { _ilike: `%${filters.source}%` } }); |
| 514 | } |
| 515 | if (filters.status) { |
| 516 | whereConditions.push({ status: { _eq: filters.status } }); |
| 517 | } |
| 518 | if (filters.container_id) { |
| 519 | // Only filter when we have a complete UUID to avoid casting errors |
| 520 | // Check if it's a valid UUID format (8-4-4-4-12 hex characters) |
| 521 | const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; |
| 522 | if (uuidRegex.test(filters.container_id)) { |
| 523 | whereConditions.push({ container_id: { _eq: filters.container_id } }); |
| 524 | } |
| 525 | // If we want to support partial matching, we'd need a computed field or custom function in Hasura |
| 526 | } |
| 527 | |