diff --git a/src/App.tsx b/src/App.tsx index 9ea9163..4a7e188 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom"; import Index from "./pages/Index"; import RegionPage from "./pages/RegionPage"; import NotFound from "./pages/NotFound"; +import SystemView from "./pages/SystemView"; const queryClient = new QueryClient(); @@ -19,6 +20,7 @@ const App = () => ( } /> } /> + } /> {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} } /> diff --git a/src/components/RegionMap.tsx b/src/components/RegionMap.tsx index e32419f..466eccc 100644 --- a/src/components/RegionMap.tsx +++ b/src/components/RegionMap.tsx @@ -102,8 +102,7 @@ export const RegionMap: React.FC<{ regionName: string }> = ({ regionName }) => { }, [systems]); const handleSystemClick = (systemName: string) => { - console.log(`Clicked system: ${systemName}`); - // Future: Navigate to system details + navigate(`/systems/${systemName}`); }; const handleMouseDown = useCallback((e: React.MouseEvent) => { diff --git a/src/components/SignatureCard.tsx b/src/components/SignatureCard.tsx new file mode 100644 index 0000000..0596784 --- /dev/null +++ b/src/components/SignatureCard.tsx @@ -0,0 +1,77 @@ + +import { Card, CardContent } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Zap, Shield, Coins, HelpCircle } from "lucide-react"; + +interface SignatureItem { + collectionId: string; + collectionName: string; + id: string; + signame: string; + sysid: string; + system: string; + type: string; +} + +interface SignatureCardProps { + signature: SignatureItem; +} + +const SignatureCard = ({ signature }: SignatureCardProps) => { + const getTypeIcon = (type: string) => { + const lowerType = type.toLowerCase(); + if (lowerType.includes('combat')) return ; + if (lowerType.includes('exploration') || lowerType.includes('relic') || lowerType.includes('data')) return ; + if (lowerType.includes('ore') || lowerType.includes('gas')) return ; + return ; + }; + + const getTypeColor = (type: string) => { + const lowerType = type.toLowerCase(); + if (lowerType.includes('combat')) return 'bg-red-900/30 text-red-300 border-red-600'; + if (lowerType.includes('exploration') || lowerType.includes('relic') || lowerType.includes('data')) return 'bg-blue-900/30 text-blue-300 border-blue-600'; + if (lowerType.includes('ore') || lowerType.includes('gas')) return 'bg-yellow-900/30 text-yellow-300 border-yellow-600'; + if (!type || type === '') return 'bg-slate-700 text-slate-300 border-slate-600'; + return 'bg-purple-900/30 text-purple-300 border-purple-600'; + }; + + return ( + + +
+ {/* Type Badge - Most Important */} +
+ + {getTypeIcon(signature.type)} + {signature.type || 'Unknown Type'} + +
+ + {/* Signature Name */} +
+

+ {signature.signame || 'Unnamed Signature'} +

+
+ + {/* Additional Info */} +
+
+ System: + {signature.system} +
+
+ ID: + {signature.id} +
+
+
+
+
+ ); +}; + +export default SignatureCard; diff --git a/src/components/SignatureListItem.tsx b/src/components/SignatureListItem.tsx new file mode 100644 index 0000000..4106d76 --- /dev/null +++ b/src/components/SignatureListItem.tsx @@ -0,0 +1,136 @@ +import { Badge } from "@/components/ui/badge"; +import { Zap, Shield, Coins, HelpCircle, Clock, AlertTriangle, Skull } from "lucide-react"; + +interface SignatureItem { + collectionId: string; + collectionName: string; + id: string; + identifier: string; + signame: string; + sysid: string; + system: string; + type: string; + updated?: string; + created?: string; + dangerous?: boolean; +} + +interface SignatureListItemProps { + signature: SignatureItem; +} + +const SignatureListItem = ({ signature }: SignatureListItemProps) => { + const getTypeIcon = (type: string) => { + const lowerType = type.toLowerCase(); + if (lowerType.includes("combat")) return ; + if (lowerType.includes("exploration") || lowerType.includes("relic") || lowerType.includes("data")) + return ; + if (lowerType.includes("ore") || lowerType.includes("gas")) return ; + return ; + }; + + const getTypeColor = (type: string, dangerous: boolean = false) => { + if (dangerous) return "bg-red-900/50 text-red-200 border-red-500"; + + const lowerType = type.toLowerCase(); + if (lowerType.includes("combat")) return "bg-red-900/30 text-red-300 border-red-600"; + if (lowerType.includes("exploration") || lowerType.includes("relic") || lowerType.includes("data")) + return "bg-blue-900/30 text-blue-300 border-blue-600"; + if (lowerType.includes("ore") || lowerType.includes("gas")) + return "bg-yellow-900/30 text-yellow-300 border-yellow-600"; + if (!type || type === "") return "bg-slate-700 text-slate-300 border-slate-600"; + return "bg-purple-900/30 text-purple-300 border-purple-600"; + }; + + const isOld = () => { + if (!signature.updated) return false; + const updatedTime = new Date(signature.updated); + if (isNaN(updatedTime.getTime())) return false; // Handle invalid date + const now = new Date(); + const diffHours = (now.getTime() - updatedTime.getTime()) / (1000 * 60 * 60); + return diffHours > 3; + }; + + const formatDate = (dateStr: string | undefined) => { + if (!dateStr) return "Unknown"; + const date = new Date(dateStr); + if (isNaN(date.getTime())) return "Invalid date"; // Handle invalid date + + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + + // Handle cases where the time difference is very small or negative + if (diffMs < 0) return "Just now"; + + const diffMinutes = Math.max(0, Math.floor(diffMs / (1000 * 60))); + const diffHours = Math.max(0, Math.floor(diffMs / (1000 * 60 * 60))); + + if (diffMinutes === 0) { + return "Just now"; + } else if (diffMinutes < 60) { + return `${diffMinutes}m ago`; + } else if (diffHours < 24) { + return `${diffHours}h ago`; + } else { + return date.toLocaleString(); + } + }; + + const oldEntry = isOld(); + + return ( +
+
+ {/* Type Badge - Most Important */} + + {signature.dangerous ? : getTypeIcon(signature.type)} + {signature.type || "Unknown Type"} + + + {/* Signature Name and ID */} +
+
+ {signature.id} +

+ {signature.signame || "Unnamed Signature"} + {signature.dangerous && ( + + DANGEROUS + + )} +

+
+
{signature.identifier}
+
+ + {/* Dates */} +
+ {signature.updated && ( +
+ {oldEntry && } + + Updated: {formatDate(signature.updated)} +
+ )} + {signature.created && ( +
+ Created: {formatDate(signature.created)} +
+ )} +
+
+
+ ); +}; + +export default SignatureListItem; diff --git a/src/components/SystemTracker.tsx b/src/components/SystemTracker.tsx new file mode 100644 index 0000000..998d2d6 --- /dev/null +++ b/src/components/SystemTracker.tsx @@ -0,0 +1,250 @@ +import { useState, useEffect } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { RefreshCw, AlertCircle, Radar } from "lucide-react"; +import SignatureListItem from "./SignatureListItem"; +import { toast } from "@/hooks/use-toast"; +import { useNavigate } from "react-router-dom"; + +interface SignatureItem { + collectionId: string; + collectionName: string; + id: string; + identifier: string; + signame: string; + sysid: string; + system: string; + type: string; + updated?: string; + created?: string; + dangerous?: boolean; +} + +interface ApiResponse { + items: SignatureItem[]; +} + +interface SystemTrackerProps { + initialSystem?: string; +} + +const SystemTracker = ({ initialSystem }: SystemTrackerProps) => { + const [currentSystem, setCurrentSystem] = useState(initialSystem || ""); + const navigate = useNavigate(); + + // Query to get current system from localhost:7000 (always enabled to track system changes) + const { data: systemData, refetch: refetchSystem, isLoading: systemLoading } = useQuery({ + queryKey: ['currentSystem'], + queryFn: async () => { + const response = await fetch('http://localhost:7000'); + if (!response.ok) { + throw new Error('Failed to fetch current system'); + } + const text = await response.text(); + return text.trim(); + }, + refetchInterval: 1000, // Always refetch to track system changes + retry: 3, + }); + + // Query to get signatures for the current system + const { + data: signaturesData, + refetch: refetchSignatures, + isLoading: signaturesLoading, + error: signaturesError + } = useQuery({ + queryKey: ['signatures', currentSystem], + queryFn: async () => { + if (!currentSystem) return null; + + const encodedSystem = encodeURIComponent(currentSystem); + const url = `https://evebase.site.quack-lab.dev/api/collections/sigview/records?filter=(system%3D'${encodedSystem}')`; + + console.log('Fetching signatures for system:', currentSystem); + console.log('API URL:', url); + + const response = await fetch(url); + if (!response.ok) { + throw new Error('Failed to fetch signatures'); + } + const data: ApiResponse = await response.json(); + return data; + }, + enabled: !!currentSystem, + refetchInterval: 60000, // Keep signature refresh at 1 minute + }); + + // Update current system when data changes and navigate to new system + useEffect(() => { + if (systemData && systemData !== currentSystem) { + setCurrentSystem(systemData); + navigate(`/${systemData}`); + console.log('Current system updated to:', systemData); + // Immediately refetch signatures when system changes + refetchSignatures(); + toast({ + title: "System Updated", + description: `Now tracking: ${systemData}`, + }); + } + }, [systemData, currentSystem, refetchSignatures, navigate]); + + // Refresh signatures every 5 seconds + useEffect(() => { + const refreshInterval = setInterval(() => { + console.log('Auto-refreshing signatures...'); + if (currentSystem) { + refetchSignatures(); + } + }, 5000); + + // Cleanup interval on component unmount + return () => clearInterval(refreshInterval); + }, [refetchSignatures, currentSystem]); + + const handleRefresh = () => { + refetchSystem(); + if (currentSystem) { + refetchSignatures(); + } + toast({ + title: "Refreshing Data", + description: "Updating system and signature information...", + }); + }; + + const signatures = signaturesData?.items || []; + const isLoading = systemLoading || signaturesLoading; + + // Sort signatures by date (newest first) and prioritize unknown types + const sortedSignatures = [...signatures].sort((a, b) => { + // First, prioritize unknown types + const aIsUnknown = !a.type || a.type === ''; + const bIsUnknown = !b.type || b.type === ''; + if (aIsUnknown && !bIsUnknown) return -1; + if (!aIsUnknown && bIsUnknown) return 1; + + // If both are unknown or both are known, sort by type + if (a.type !== b.type) { + return a.type.localeCompare(b.type); + } + + // If same type, sort by date + const dateA = a.updated || a.created || ''; + const dateB = b.updated || b.created || ''; + return dateB.localeCompare(dateA); + }); + + // Group signatures by type for better organization + const signaturesByType = sortedSignatures.reduce((acc, sig) => { + const type = sig.type || 'Unknown'; + if (!acc[type]) acc[type] = []; + acc[type].push(sig); + return acc; + }, {} as Record); + + return ( +
+ {/* System Status Card */} + + + + + {initialSystem ? 'System View' : 'Current System'} + + + + +
+
+ {systemLoading && !initialSystem ? ( +
Loading system...
+ ) : currentSystem ? ( +
{currentSystem}
+ ) : ( +
No system data
+ )} +
+ + {signatures.length} Signatures + +
+
+
+ + {/* Error Display */} + {signaturesError && ( + + +
+ + Error loading signatures: {signaturesError.message} +
+
+
+ )} + + {/* Signatures Display */} + {currentSystem && !signaturesLoading && ( +
+ {sortedSignatures.length === 0 ? ( + + +
No signatures found for {currentSystem}
+
+
+ ) : ( + + + + Signatures + + {sortedSignatures.length} Total + + + + +
+ {sortedSignatures.map((signature) => ( + + ))} +
+
+
+ )} +
+ )} + + {/* Loading State */} + {signaturesLoading && currentSystem && ( + + +
+ {[...Array(6)].map((_, i) => ( +
+
+
+
+
+ ))} +
+
+
+ )} +
+ ); +}; + +export default SystemTracker; diff --git a/src/pages/SystemView.tsx b/src/pages/SystemView.tsx new file mode 100644 index 0000000..1e67236 --- /dev/null +++ b/src/pages/SystemView.tsx @@ -0,0 +1,26 @@ +import { useParams, useNavigate } from "react-router-dom"; +import SystemTracker from "@/components/SystemTracker"; + +const SystemView = () => { + const { system } = useParams(); + const navigate = useNavigate(); + + if (!system) { + navigate("/"); + return null; + } + + return ( +
+
+
+

Cosmic Region Navigator

+

Viewing signatures for system: {system}

+
+ +
+
+ ); +}; + +export default SystemView; \ No newline at end of file