feat(api): restructure api with axios and chainage related changes
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
Layers,
|
||||
Package,
|
||||
MapPin,
|
||||
Milestone,
|
||||
} from "lucide-react";
|
||||
import { NavUser } from "@/components/nav-user";
|
||||
import { ROUTES } from "@/utils/routes";
|
||||
@@ -53,9 +54,9 @@ const data = {
|
||||
icon: Package,
|
||||
},
|
||||
{
|
||||
title: "Location",
|
||||
url: ROUTES.LOCATION,
|
||||
icon: MapPin,
|
||||
title: "Chainage",
|
||||
url: ROUTES.CHAINAGE,
|
||||
icon: Milestone,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type ChartConfig,
|
||||
} from "@/components/ui/chart"
|
||||
|
||||
interface LocationData {
|
||||
interface ChainageData {
|
||||
name: string
|
||||
defected_sign_board: number
|
||||
pothole: number
|
||||
@@ -20,8 +20,8 @@ interface LocationData {
|
||||
total: number
|
||||
}
|
||||
|
||||
interface LocationBarChartProps {
|
||||
data: LocationData[]
|
||||
interface ChainageBarChartProps {
|
||||
data: ChainageData[]
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const chartConfig = {
|
||||
},
|
||||
} satisfies ChartConfig
|
||||
|
||||
export function LocationBarChart({ data, isLoading = false }: LocationBarChartProps) {
|
||||
export function ChainageBarChart({ data, isLoading = false }: ChainageBarChartProps) {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="h-[250px] flex items-center justify-center">
|
||||
@@ -56,8 +56,8 @@ export function LocationBarChart({ data, isLoading = false }: LocationBarChartPr
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="h-[250px] flex flex-col items-center justify-center text-muted-foreground">
|
||||
<p className="text-sm">No location data available</p>
|
||||
<p className="text-xs mt-1">Process videos to see detections by location</p>
|
||||
<p className="text-sm">No chainage data available</p>
|
||||
<p className="text-xs mt-1">Process videos to see detections by chainage</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
import { useEffect, useState } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||
import { Loader2, MapPin } from "lucide-react"
|
||||
import { Loader2, Milestone } from "lucide-react"
|
||||
import { Detection } from "@/types"
|
||||
|
||||
// Dynamically import the map to avoid SSR issues with Leaflet
|
||||
@@ -23,7 +23,7 @@ interface DashboardMapProps {
|
||||
className?: string
|
||||
selectedProjectId?: string | null
|
||||
selectedPackageId?: string | null
|
||||
selectedLocationId?: string | null
|
||||
selectedChainageId?: string | null
|
||||
projectSummary?: any
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function DashboardMap({
|
||||
className,
|
||||
selectedProjectId,
|
||||
selectedPackageId,
|
||||
selectedLocationId,
|
||||
selectedChainageId,
|
||||
projectSummary
|
||||
}: DashboardMapProps) {
|
||||
const [detections, setDetections] = useState<Detection[]>([])
|
||||
@@ -56,14 +56,14 @@ export function DashboardMap({
|
||||
: projectSummary.packages || {}
|
||||
|
||||
for (const [pkgName, pkg] of Object.entries(packagesToProcess)) {
|
||||
const locationsToProcess = selectedLocationId && selectedLocationId !== "all"
|
||||
? { [selectedLocationId]: (pkg as any).locations[selectedLocationId] }
|
||||
: (pkg as any).locations || {}
|
||||
const chainagesToProcess = selectedChainageId && selectedChainageId !== "all"
|
||||
? { [selectedChainageId]: (pkg as any).chainages[selectedChainageId] }
|
||||
: (pkg as any).chainages || {}
|
||||
|
||||
for (const [locName, loc] of Object.entries(locationsToProcess)) {
|
||||
if (!loc) continue
|
||||
const locationDetections = (loc as any).detections || []
|
||||
filteredDetections.push(...locationDetections)
|
||||
for (const [chnName, chn] of Object.entries(chainagesToProcess)) {
|
||||
if (!chn) continue
|
||||
const chainageDetections = (chn as any).detections || []
|
||||
filteredDetections.push(...chainageDetections)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ export function DashboardMap({
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [projectSummary, selectedPackageId, selectedLocationId])
|
||||
}, [projectSummary, selectedPackageId, selectedChainageId])
|
||||
|
||||
// Filter detections with valid GPS coordinates
|
||||
const validDetections = detections.filter(d => d.latitude && d.longitude)
|
||||
@@ -94,7 +94,7 @@ export function DashboardMap({
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 rounded-md bg-secondary text-secondary-foreground flex items-center justify-center">
|
||||
<MapPin className="h-5 w-5" />
|
||||
<Milestone className="h-5 w-5" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base font-bold">
|
||||
|
||||
@@ -7,19 +7,19 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { FolderOpen, Package, MapPin } from "lucide-react"
|
||||
import { FolderOpen, Package, Milestone } from "lucide-react"
|
||||
import { Project } from "@/types"
|
||||
|
||||
interface FilterSelectorProps {
|
||||
projects: Project[]
|
||||
selectedProjectId: string | null
|
||||
selectedPackageId: string | null
|
||||
selectedLocationId: string | null
|
||||
selectedChainageId: string | null
|
||||
onProjectChange: (projectId: string) => void
|
||||
onPackageChange: (packageId: string) => void
|
||||
onLocationChange: (locationId: string) => void
|
||||
onChainageChange: (chainageId: string) => void
|
||||
packages: Array<{ id: string; name: string }>
|
||||
locations: Array<{ id: string; name: string }>
|
||||
chainages: Array<{ id: string; name: string }>
|
||||
isLoading?: boolean
|
||||
}
|
||||
|
||||
@@ -27,12 +27,12 @@ export function FilterSelector({
|
||||
projects,
|
||||
selectedProjectId,
|
||||
selectedPackageId,
|
||||
selectedLocationId,
|
||||
selectedChainageId,
|
||||
onProjectChange,
|
||||
onPackageChange,
|
||||
onLocationChange,
|
||||
onChainageChange,
|
||||
packages,
|
||||
locations,
|
||||
chainages,
|
||||
isLoading = false
|
||||
}: FilterSelectorProps) {
|
||||
return (
|
||||
@@ -92,28 +92,28 @@ export function FilterSelector({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Location Dropdown */}
|
||||
{/* Chainage Dropdown */}
|
||||
{selectedPackageId && selectedPackageId !== "all" && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="p-2 rounded-lg bg-gray-900 dark:bg-gray-100 text-gray-100 dark:text-gray-900 shadow-sm">
|
||||
<MapPin className="h-4 w-4" />
|
||||
<Milestone className="h-4 w-4" />
|
||||
</div>
|
||||
<span className="text-[11px] font-black text-muted-foreground uppercase tracking-[0.15em]">Location</span>
|
||||
<span className="text-[11px] font-black text-muted-foreground uppercase tracking-[0.15em]">Chainage</span>
|
||||
</div>
|
||||
<Select
|
||||
value={selectedLocationId || "all"}
|
||||
onValueChange={onLocationChange}
|
||||
value={selectedChainageId || "all"}
|
||||
onValueChange={onChainageChange}
|
||||
disabled={isLoading}
|
||||
>
|
||||
<SelectTrigger className="h-10 text-sm w-full bg-background border-border/60 shadow-xs">
|
||||
<SelectValue placeholder="All locations" />
|
||||
<SelectValue placeholder="All chainages" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">All Locations</SelectItem>
|
||||
{locations.map((loc) => (
|
||||
<SelectItem key={loc.id} value={loc.id}>
|
||||
{loc.name}
|
||||
<SelectItem value="all">All Chainages</SelectItem>
|
||||
{chainages.map((chn) => (
|
||||
<SelectItem key={chn.id} value={chn.id}>
|
||||
{chn.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
|
||||
@@ -6,15 +6,11 @@ import { Button } from "@/components/ui/button"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||
import { Loader2, Check } from "lucide-react"
|
||||
import {
|
||||
fetchProjects,
|
||||
fetchPackagesByProject,
|
||||
fetchLocationsByPackage,
|
||||
} from "@/lib/api"
|
||||
import { projectService, packageService, chainageService } from "@/services/api"
|
||||
import {
|
||||
Project,
|
||||
Package as PackageType,
|
||||
Location,
|
||||
Chainage,
|
||||
SessionContext
|
||||
} from "@/types"
|
||||
import { cn } from "@/lib/utils"
|
||||
@@ -27,17 +23,17 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
// Data states
|
||||
const [projects, setProjects] = useState<Project[]>([])
|
||||
const [packages, setPackages] = useState<PackageType[]>([])
|
||||
const [locations, setLocations] = useState<Location[]>([])
|
||||
const [chainages, setChainages] = useState<Chainage[]>([])
|
||||
|
||||
// Selection states
|
||||
const [selectedProject, setSelectedProject] = useState<Project | null>(null)
|
||||
const [selectedPackage, setSelectedPackage] = useState<PackageType | null>(null)
|
||||
const [selectedLocation, setSelectedLocation] = useState<Location | null>(null)
|
||||
const [selectedChainage, setSelectedChainage] = useState<Chainage | null>(null)
|
||||
|
||||
// Loading states
|
||||
const [loadingProjects, setLoadingProjects] = useState(true)
|
||||
const [loadingPackages, setLoadingPackages] = useState(false)
|
||||
const [loadingLocations, setLoadingLocations] = useState(false)
|
||||
const [loadingChainages, setLoadingChainages] = useState(false)
|
||||
|
||||
// Error state
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -48,7 +44,7 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
try {
|
||||
setLoadingProjects(true)
|
||||
setError(null)
|
||||
const data = await fetchProjects()
|
||||
const data = await projectService.getProjects()
|
||||
setProjects(data.items)
|
||||
} catch (err) {
|
||||
console.error("Failed to load projects:", err)
|
||||
@@ -73,9 +69,9 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
setLoadingPackages(true)
|
||||
setError(null)
|
||||
setSelectedPackage(null)
|
||||
setSelectedLocation(null)
|
||||
setLocations([])
|
||||
const data = await fetchPackagesByProject(selectedProject.id)
|
||||
setSelectedChainage(null)
|
||||
setChainages([])
|
||||
const data = await packageService.getPackagesByProject(selectedProject.id)
|
||||
setPackages(data.items)
|
||||
} catch (err) {
|
||||
console.error("Failed to load packages:", err)
|
||||
@@ -87,29 +83,29 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
loadPackages()
|
||||
}, [selectedProject])
|
||||
|
||||
// Load locations when package changes
|
||||
// Load chainages when package changes
|
||||
useEffect(() => {
|
||||
if (!selectedPackage) {
|
||||
setLocations([])
|
||||
setSelectedLocation(null)
|
||||
setChainages([])
|
||||
setSelectedChainage(null)
|
||||
return
|
||||
}
|
||||
|
||||
const loadLocations = async () => {
|
||||
const loadChainages = async () => {
|
||||
try {
|
||||
setLoadingLocations(true)
|
||||
setLoadingChainages(true)
|
||||
setError(null)
|
||||
setSelectedLocation(null)
|
||||
const data = await fetchLocationsByPackage(selectedPackage.id)
|
||||
setLocations(data.items)
|
||||
setSelectedChainage(null)
|
||||
const data = await chainageService.getChainagesByPackage(selectedPackage.id)
|
||||
setChainages(data.items)
|
||||
} catch (err) {
|
||||
console.error("Failed to load locations:", err)
|
||||
setError("Failed to load locations for the selected package.")
|
||||
console.error("Failed to load chainages:", err)
|
||||
setError("Failed to load chainages for the selected package.")
|
||||
} finally {
|
||||
setLoadingLocations(false)
|
||||
setLoadingChainages(false)
|
||||
}
|
||||
}
|
||||
loadLocations()
|
||||
loadChainages()
|
||||
}, [selectedPackage])
|
||||
|
||||
const handleProjectChange = (projectId: string) => {
|
||||
@@ -122,31 +118,31 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
setSelectedPackage(pkg)
|
||||
}
|
||||
|
||||
const handleLocationChange = (locationId: string) => {
|
||||
const location = locations.find(l => l.id === locationId) || null
|
||||
setSelectedLocation(location)
|
||||
const handleChainageChange = (chainageId: string) => {
|
||||
const chainage = chainages.find(chn => chn.id === chainageId) || null
|
||||
setSelectedChainage(chainage)
|
||||
}
|
||||
|
||||
const handleProceed = () => {
|
||||
if (selectedProject && selectedPackage && selectedLocation) {
|
||||
if (selectedProject && selectedPackage && selectedChainage) {
|
||||
onSelectionComplete({
|
||||
projectId: selectedProject.id,
|
||||
projectName: selectedProject.name,
|
||||
packageId: selectedPackage.id,
|
||||
packageName: selectedPackage.name,
|
||||
locationId: selectedLocation.id,
|
||||
locationName: selectedLocation.segment_name
|
||||
chainageId: selectedChainage.id,
|
||||
chainageName: selectedChainage.segment_name
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const isComplete = selectedProject && selectedPackage && selectedLocation
|
||||
const isComplete = selectedProject && selectedPackage && selectedChainage
|
||||
|
||||
// Step status helpers
|
||||
const getStepStatus = (step: number) => {
|
||||
if (step === 1) return selectedProject ? 'completed' : 'active'
|
||||
if (step === 2) return selectedPackage ? 'completed' : selectedProject ? 'active' : 'pending'
|
||||
if (step === 3) return selectedLocation ? 'completed' : selectedPackage ? 'active' : 'pending'
|
||||
if (step === 3) return selectedChainage ? 'completed' : selectedPackage ? 'active' : 'pending'
|
||||
return 'pending'
|
||||
}
|
||||
|
||||
@@ -155,9 +151,9 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
<CardHeader className="pb-6">
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<CardTitle className="text-2xl font-bold">Select Project Location</CardTitle>
|
||||
<CardTitle className="text-2xl font-bold">Select Project Chainage</CardTitle>
|
||||
<CardDescription className="mt-2 text-base">
|
||||
Select Project, Package & Location to begin intelligent road analysis.
|
||||
Select Project, Package & Chainage to begin intelligent road analysis.
|
||||
</CardDescription>
|
||||
</div>
|
||||
|
||||
@@ -165,7 +161,7 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
<div className="flex items-center justify-center pt-2">
|
||||
{[1, 2, 3].map((step, index) => {
|
||||
const status = getStepStatus(step)
|
||||
const labels = ['Project', 'Package', 'Location']
|
||||
const labels = ['Project', 'Package', 'Chainage']
|
||||
return (
|
||||
<div key={step} className="flex items-center">
|
||||
<div className="flex flex-col items-center">
|
||||
@@ -268,30 +264,35 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Location Dropdown */}
|
||||
{/* Chainage Dropdown */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="location" className="text-sm font-semibold">
|
||||
Location
|
||||
<Label htmlFor="chainage" className="text-sm font-semibold">
|
||||
Chainage
|
||||
</Label>
|
||||
<Select
|
||||
value={selectedLocation?.id || ""}
|
||||
onValueChange={handleLocationChange}
|
||||
disabled={!selectedPackage || loadingLocations}
|
||||
value={selectedChainage?.id || ""}
|
||||
onValueChange={handleChainageChange}
|
||||
disabled={!selectedPackage || loadingChainages}
|
||||
>
|
||||
<SelectTrigger id="location" className="h-11">
|
||||
{loadingLocations ? (
|
||||
<SelectTrigger id="chainage" className="h-11">
|
||||
{loadingChainages ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span>Loading...</span>
|
||||
</div>
|
||||
) : (
|
||||
<SelectValue placeholder={selectedPackage ? "Select location" : "Select package first"} />
|
||||
<SelectValue placeholder={selectedPackage ? "Select chainage" : "Select package first"} />
|
||||
)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{locations.map((location) => (
|
||||
<SelectItem key={location.id} value={location.id}>
|
||||
{location.segment_name}
|
||||
{chainages.map((chn) => (
|
||||
<SelectItem key={chn.id} value={chn.id}>
|
||||
<div className="flex flex-col">
|
||||
<span>{chn.segment_name}</span>
|
||||
<span className="text-[10px] text-muted-foreground">
|
||||
{chn.chainage_start_km}-{chn.chainage_end_km} km | {chn.direction}
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
@@ -308,7 +309,7 @@ export function ProjectSelectionSection({ onSelectionComplete }: ProjectSelectio
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{selectedPackage?.name}</span>
|
||||
<span>/</span>
|
||||
<span className="text-foreground">{selectedLocation?.segment_name}</span>
|
||||
<span className="text-foreground">{selectedChainage?.segment_name}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { Upload, Loader2, AlertCircle } from "lucide-react"
|
||||
import type { DetectionData, DetectionType } from "@/types"
|
||||
|
||||
import { videoService } from "@/services/api"
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL
|
||||
const WS_URL = API_URL?.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://")
|
||||
|
||||
@@ -109,17 +110,7 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
|
||||
const loadResults = async (videoId: string) => {
|
||||
try {
|
||||
console.log("[Upload] Loading results for:", videoId)
|
||||
const response = await fetch(`${API_URL}/results/${videoId}`, {
|
||||
headers: {
|
||||
"ngrok-skip-browser-warning": "true"
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load results: ${response.status}`)
|
||||
}
|
||||
|
||||
const detectionData: DetectionData = await response.json()
|
||||
const detectionData: DetectionData = await videoService.getVideoResults(videoId);
|
||||
console.log("[Upload] Results loaded:", detectionData)
|
||||
|
||||
setUploading(false)
|
||||
@@ -162,29 +153,7 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
console.log("[Upload] Uploading to:", `${API_URL}/upload`)
|
||||
console.log("[Upload] Detection type:", detectionType)
|
||||
console.log("[Upload] FormData contents:")
|
||||
console.log(" - file:", file.name)
|
||||
console.log(" - detection_type:", detectionType)
|
||||
console.log(" - speed_kmh:", speed)
|
||||
|
||||
const response = await fetch(`${API_URL}/upload`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"ngrok-skip-browser-warning": "true"
|
||||
},
|
||||
body: formData
|
||||
})
|
||||
|
||||
console.log("[Upload] Upload response status:", response.status)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Upload failed (${response.status}): ${errorText}`)
|
||||
}
|
||||
|
||||
const result = await response.json()
|
||||
const result = await videoService.uploadVideo(formData);
|
||||
const videoId = result.video_id
|
||||
|
||||
console.log("[Upload] Video uploaded successfully, ID:", videoId)
|
||||
@@ -200,7 +169,7 @@ export function UploadSection({ onDetectionComplete, onDetectionTypeChange }: Up
|
||||
let errorMessage = "Upload failed"
|
||||
|
||||
if (error instanceof TypeError && error.message === "Failed to fetch") {
|
||||
errorMessage = "Cannot connect to server. Please check:\n• Backend is running on " + API_URL + "\n• CORS is configured properly\n• No firewall blocking the connection"
|
||||
errorMessage = "Cannot connect to server. Please check if backend is running."
|
||||
} else if (error instanceof Error) {
|
||||
errorMessage = error.message
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const MapModal = dynamic(() => import("@/components/map-modal"), { ssr: false })
|
||||
import { DetectionData, DetectionType } from "@/types"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL
|
||||
import { projectService } from "@/services/api"
|
||||
|
||||
type VideoPlayerSectionProps = {
|
||||
data: DetectionData
|
||||
@@ -37,7 +37,7 @@ type DetectionLog = {
|
||||
videoTime: string // Video timestamp (MM:SS)
|
||||
}
|
||||
|
||||
type LocationSummaryData = {
|
||||
type ChainageSummaryData = {
|
||||
project: {
|
||||
id: string
|
||||
name: string
|
||||
@@ -48,9 +48,9 @@ type LocationSummaryData = {
|
||||
[packageName: string]: {
|
||||
package_id: string
|
||||
region: string | null
|
||||
locations: {
|
||||
[locationName: string]: {
|
||||
location_id: string
|
||||
chainages: {
|
||||
[chainageName: string]: {
|
||||
chainage_id: string
|
||||
chainage: string | null
|
||||
detection_count: number
|
||||
detections: Array<{
|
||||
@@ -87,7 +87,7 @@ function DetailedSummarySection({
|
||||
show: boolean
|
||||
detectionType: DetectionType
|
||||
}) {
|
||||
const [summaryData, setSummaryData] = useState<LocationSummaryData | null>(null)
|
||||
const [summaryData, setSummaryData] = useState<ChainageSummaryData | null>(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [showMap, setShowMap] = useState(false)
|
||||
|
||||
@@ -97,15 +97,8 @@ function DetailedSummarySection({
|
||||
const fetchSummary = async () => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/summary/projects/${projectId}?video_id=${videoId}`, {
|
||||
headers: { "ngrok-skip-browser-warning": "true" }
|
||||
})
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setSummaryData(data)
|
||||
} else {
|
||||
console.error(`Failed to fetch summary: ${response.status}`)
|
||||
}
|
||||
const data = await projectService.getProjectSummaryByVideo(projectId, videoId);
|
||||
setSummaryData(data)
|
||||
} catch (err) {
|
||||
console.error("Failed to fetch summary:", err)
|
||||
} finally {
|
||||
@@ -124,14 +117,14 @@ function DetailedSummarySection({
|
||||
// Flatten all detections for the scrollable list
|
||||
const allDetections: Array<{
|
||||
detection: any
|
||||
locationName: string
|
||||
chainageName: string
|
||||
packageName: string
|
||||
}> = []
|
||||
|
||||
Object.entries(summaryData.packages || {}).forEach(([packageName, packageData]) => {
|
||||
Object.entries(packageData?.locations || {}).forEach(([locationName, locationData]) => {
|
||||
locationData?.detections?.forEach(detection => {
|
||||
allDetections.push({ detection, locationName, packageName })
|
||||
Object.entries(packageData?.chainages || {}).forEach(([chainageName, chainageData]) => {
|
||||
chainageData?.detections?.forEach(detection => {
|
||||
allDetections.push({ detection, chainageName, packageName })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -147,7 +140,7 @@ function DetailedSummarySection({
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-base font-bold">
|
||||
Locations
|
||||
Chainages
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
{isCombined ? "Potholes & Signboards" : isPothole ? "Potholes" : "Signboards"} detected
|
||||
@@ -186,16 +179,16 @@ function DetailedSummarySection({
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Packages and Locations */}
|
||||
{/* Packages and Chainages */}
|
||||
{Object.entries(summaryData.packages).map(([packageName, packageData]) => (
|
||||
<div key={packageData.package_id} className="space-y-2">
|
||||
<div className="text-xs font-bold text-muted-foreground truncate">{packageName}</div>
|
||||
<div className="pl-2 space-y-2 border-l-2 border-muted">
|
||||
{Object.entries(packageData.locations).map(([locationName, locationData]) => (
|
||||
<div key={locationData.location_id} className="text-xs p-3 rounded-md bg-muted/50 flex items-center justify-between gap-4">
|
||||
<div className="font-semibold truncate">{locationName}</div>
|
||||
{Object.entries(packageData.chainages).map(([chainageName, chainageData]) => (
|
||||
<div key={chainageData.chainage_id} className="text-xs p-3 rounded-md bg-muted/50 flex items-center justify-between gap-4">
|
||||
<div className="font-semibold truncate">{chainageName}</div>
|
||||
<div className="text-[10px] bg-background px-2 py-0.5 rounded border font-bold">
|
||||
{locationData.detection_count}
|
||||
{chainageData.detection_count}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -226,7 +219,7 @@ function DetailedSummarySection({
|
||||
<CardContent className="flex-1 p-0 overflow-hidden">
|
||||
<ScrollArea className="h-[300px] p-4">
|
||||
<div className="space-y-2">
|
||||
{allDetections.map(({ detection, locationName }, idx) => (
|
||||
{allDetections.map(({ detection, chainageName }, idx) => (
|
||||
<div
|
||||
key={`${detection.id}-${idx}`}
|
||||
className="text-xs p-3 bg-muted/30 border rounded-md hover:bg-muted/50 transition-colors"
|
||||
@@ -240,7 +233,7 @@ function DetailedSummarySection({
|
||||
</Badge>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 text-[10px] text-muted-foreground">
|
||||
<div className="truncate">Location: <span className="text-foreground font-medium">{locationName}</span></div>
|
||||
<div className="truncate">Chainage: <span className="text-foreground font-medium">{chainageName}</span></div>
|
||||
<div>Confidence: <span className="text-foreground font-medium">{(detection.confidence * 100).toFixed(1)}%</span></div>
|
||||
<div className="col-span-2 font-mono bg-muted/50 p-1 rounded border">
|
||||
GPS: {detection.latitude}, {detection.longitude}
|
||||
@@ -512,7 +505,7 @@ export default function VideoPlayerSection({ data, videoId, videoFile, detection
|
||||
useEffect(() => {
|
||||
const video = videoRef.current
|
||||
if (!video) return
|
||||
const videoUrl = videoFile ? URL.createObjectURL(videoFile) : (videoId ? `${API_URL}/video/${videoId}` : '')
|
||||
const videoUrl = videoFile ? URL.createObjectURL(videoFile) : (videoId ? `${process.env.NEXT_PUBLIC_API_URL}/video/${videoId}` : '')
|
||||
if (videoUrl) {
|
||||
video.src = videoUrl
|
||||
video.onloadeddata = resizeCanvas
|
||||
|
||||
Reference in New Issue
Block a user