From 598098d7e8cba48f1a497c7a912ceab154e24017 Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Wed, 18 Mar 2026 18:42:40 +0530 Subject: [PATCH] feat(api): restructure api with axios and chainage related changes --- package-lock.json | 15 + .../(modules)/{location => chainage}/page.tsx | 220 ++++++----- src/app/(modules)/dashboard/page.tsx | 291 +++++++------- src/app/(modules)/new-analysis/page.tsx | 4 +- src/app/(modules)/package/page.tsx | 72 +++- src/app/(modules)/project/page.tsx | 10 +- src/app/(modules)/results/[videoId]/page.tsx | 26 +- src/app/(modules)/results/page.tsx | 19 +- src/app/(modules)/upload/[videoId]/page.tsx | 25 +- src/app/(modules)/upload/page.tsx | 32 +- src/components/app-sidebar.tsx | 7 +- ...n-bar-chart.tsx => chainage-bar-chart.tsx} | 12 +- src/components/dashboard/dashboard-map.tsx | 24 +- src/components/dashboard/filter-selector.tsx | 34 +- src/components/project-selection-section.tsx | 99 ++--- src/components/upload-section.tsx | 39 +- src/components/video-player-section.tsx | 49 +-- src/lib/api.ts | 365 ------------------ src/lib/project-service.ts | 49 --- src/services/api/chainage.service.ts | 58 +++ src/services/api/detection.service.ts | 56 +++ src/services/api/index.ts | 7 + src/services/api/package.service.ts | 58 +++ src/services/api/project.service.ts | 100 +++++ src/services/api/session.service.ts | 73 ++++ src/services/api/video.service.ts | 79 ++++ src/types/{location.ts => chainage.ts} | 23 +- src/types/index.ts | 2 +- src/types/package.ts | 6 + src/types/session.ts | 8 +- src/utils/routes.ts | 2 +- 31 files changed, 933 insertions(+), 931 deletions(-) rename src/app/(modules)/{location => chainage}/page.tsx (79%) rename src/components/dashboard/{location-bar-chart.tsx => chainage-bar-chart.tsx} (91%) delete mode 100644 src/lib/api.ts delete mode 100644 src/lib/project-service.ts create mode 100644 src/services/api/chainage.service.ts create mode 100644 src/services/api/detection.service.ts create mode 100644 src/services/api/index.ts create mode 100644 src/services/api/package.service.ts create mode 100644 src/services/api/project.service.ts create mode 100644 src/services/api/session.service.ts create mode 100644 src/services/api/video.service.ts rename src/types/{location.ts => chainage.ts} (53%) diff --git a/package-lock.json b/package-lock.json index 898b0f9..134d906 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4937,6 +4937,21 @@ "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.0.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.10.tgz", + "integrity": "sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/src/app/(modules)/location/page.tsx b/src/app/(modules)/chainage/page.tsx similarity index 79% rename from src/app/(modules)/location/page.tsx rename to src/app/(modules)/chainage/page.tsx index 6e574c2..132b53c 100644 --- a/src/app/(modules)/location/page.tsx +++ b/src/app/(modules)/chainage/page.tsx @@ -7,33 +7,25 @@ import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog" -import { Loader2, CheckCircle2, MapPin, Navigation, Milestone } from "lucide-react" +import { Loader2, CheckCircle2, MapPin, Navigation, Milestone, ArrowUpCircle, ArrowDownCircle } from "lucide-react" import { DataTable } from "@/components/data-table" import { PageHeader } from "@/components/page-header" import { PoweredBy } from "@/components/powered-by" -import { - fetchProjects, - fetchPackagesByProject, - fetchAllLocations, - fetchAllPackages, - createLocation, - updateLocation, - deleteLocation -} from "@/lib/api" +import { projectService, packageService, chainageService } from "@/services/api" import { Project, Package as PackageType, - Location, - LocationCreate, - LocationUpdate + Chainage, + ChainageCreate, + ChainageUpdate } from "@/types" import { ColumnDef } from "@tanstack/react-table" import { toast } from "sonner" import { Badge } from "@/components/ui/badge" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" -export default function LocationPage() { - const [locations, setLocations] = useState([]) +export default function ChainagePage() { + const [chainages, setChainages] = useState([]) const [totalItems, setTotalItems] = useState(0) const [projects, setProjects] = useState([]) const [packages, setPackages] = useState([]) @@ -51,7 +43,7 @@ export default function LocationPage() { // Editing state const [isEditing, setIsEditing] = useState(false) - const [currentLocation, setCurrentLocation] = useState(null) + const [currentChainage, setCurrentChainage] = useState(null) // Form fields const [selectedProjectId, setSelectedProjectId] = useState("") @@ -63,17 +55,18 @@ export default function LocationPage() { const [startLng, setStartLng] = useState("") const [endLat, setEndLat] = useState("") const [endLng, setEndLng] = useState("") + const [direction, setDirection] = useState<'UP' | 'DOWN'>("UP") - // Load locations and projects - const loadLocations = async (currentSkip = skip, currentLimit = limit) => { + // Load chainages and projects + const loadChainages = async (currentSkip = skip, currentLimit = limit) => { try { setIsLoading(true) setError(null) - const data = await fetchAllLocations({ skip: currentSkip, limit: currentLimit }) - setLocations(data.items) + const data = await chainageService.getChainages({ skip: currentSkip, limit: currentLimit }) + setChainages(data.items) setTotalItems(data.totalItems) } catch (err) { - setError("Failed to load locations. Please check if the backend is running.") + setError("Failed to load chainages. Please check if the backend is running.") } finally { // Add a small delay for animation stability setTimeout(() => { @@ -85,7 +78,7 @@ export default function LocationPage() { const loadProjects = async () => { try { setLoadingProjects(true) - const data = await fetchProjects({ skip: 0, limit: 1000 }) + const data = await projectService.getProjects({ skip: 0, limit: 1000 }) setProjects(data.items) } catch (err) { setError("Failed to load projects.") @@ -96,7 +89,7 @@ export default function LocationPage() { const loadAllPackages = async () => { try { - const data = await fetchAllPackages({ skip: 0, limit: 1000 }) + const data = await packageService.getPackages({ skip: 0, limit: 1000 }) setAllPackages(data.items) } catch (err) { console.error("Failed to load all packages") @@ -104,7 +97,7 @@ export default function LocationPage() { } useEffect(() => { - loadLocations(skip, limit) + loadChainages(skip, limit) loadProjects() loadAllPackages() }, [skip, limit]) @@ -121,7 +114,7 @@ export default function LocationPage() { try { setLoadingPackages(true) if (!isEditing) setSelectedPackageId("") - const data = await fetchPackagesByProject(selectedProjectId, { skip: 0, limit: 1000 }) + const data = await packageService.getPackagesByProject(selectedProjectId, { skip: 0, limit: 1000 }) setPackages(data.items) } catch (err) { setError("Failed to load packages for the selected project.") @@ -142,9 +135,10 @@ export default function LocationPage() { setStartLng("") setEndLat("") setEndLng("") + setDirection("UP") setError(null) setIsEditing(false) - setCurrentLocation(null) + setCurrentChainage(null) } const handleSubmit = async (e: React.FormEvent) => { @@ -158,7 +152,11 @@ export default function LocationPage() { return } if (!startLat || !startLng || !endLat || !endLng) { - setError("All GPS coordinates are required for locations") + setError("All GPS coordinates are required for chainages") + return + } + if (!chainageStartKm || !chainageEndKm) { + setError("Chainage start and end values are required") return } @@ -166,45 +164,47 @@ export default function LocationPage() { setError(null) try { - if (isEditing && currentLocation) { - const data: LocationUpdate = { + if (isEditing && currentChainage) { + const data: ChainageUpdate = { segment_name: segmentName.trim(), - chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : null, - chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : null, + chainage_start_km: parseFloat(chainageStartKm), + chainage_end_km: parseFloat(chainageEndKm), start_lat: parseFloat(startLat), start_lng: parseFloat(startLng), end_lat: parseFloat(endLat), end_lng: parseFloat(endLng), + direction: direction as 'UP' | 'DOWN', } - await updateLocation(currentLocation.id, data) - toast.success("Location Updated", { + await chainageService.updateChainage(currentChainage.id, data) + toast.success("Chainage Updated", { description: `${segmentName} has been updated successfully at ${new Date().toLocaleTimeString()}`, }) } else { - const data: LocationCreate = { + const data: ChainageCreate = { package_id: selectedPackageId, segment_name: segmentName.trim(), - chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : null, - chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : null, + chainage_start_km: parseFloat(chainageStartKm), + chainage_end_km: parseFloat(chainageEndKm), start_lat: parseFloat(startLat), start_lng: parseFloat(startLng), end_lat: parseFloat(endLat), end_lng: parseFloat(endLng), + direction: direction, } - await createLocation(data) - toast.success("Location Created", { + await chainageService.createChainage(data) + toast.success("Chainage Created", { description: `${segmentName} has been established successfully at ${new Date().toLocaleTimeString()}`, }) } - // Refresh locations list - await loadLocations() + // Refresh chainages list + await loadChainages() // Close modal and reset form immediately setIsModalOpen(false) resetForm() } catch (err) { - const message = err instanceof Error ? err.message : `Failed to ${isEditing ? 'update' : 'create'} location` + const message = err instanceof Error ? err.message : `Failed to ${isEditing ? 'update' : 'create'} chainage` setError(message) toast.error("Operation Failed", { description: message, @@ -214,41 +214,42 @@ export default function LocationPage() { } } - const handleEdit = (location: Location) => { + const handleEdit = (chainage: Chainage) => { setIsEditing(true) - setCurrentLocation(location) + setCurrentChainage(chainage) // Find project for this package - const pkg = allPackages.find(p => p.id === location.package_id) + const pkg = allPackages.find(p => p.id === chainage.package_id) if (pkg) { setSelectedProjectId(pkg.project_id) - setSelectedPackageId(location.package_id) + setSelectedPackageId(chainage.package_id) } - setSegmentName(location.segment_name || "") - setChainageStartKm(location.chainage_start_km?.toString() || "") - setChainageEndKm(location.chainage_end_km?.toString() || "") - setStartLat(location.start_lat.toString()) - setStartLng(location.start_lng.toString()) - setEndLat(location.end_lat.toString()) - setEndLng(location.end_lng.toString()) + setSegmentName(chainage.segment_name || "") + setChainageStartKm(chainage.chainage_start_km?.toString() || "") + setChainageEndKm(chainage.chainage_end_km?.toString() || "") + setStartLat(chainage.start_lat.toString()) + setStartLng(chainage.start_lng.toString()) + setEndLat(chainage.end_lat.toString()) + setEndLng(chainage.end_lng.toString()) + setDirection(chainage.direction) setIsModalOpen(true) } - const handleDelete = async (location: Location) => { - if (!confirm(`Are you sure you want to delete location "${location.segment_name}"?`)) return + const handleDelete = async (chainage: Chainage) => { + if (!confirm(`Are you sure you want to delete chainage "${chainage.segment_name}"?`)) return try { setIsLoading(true) - await deleteLocation(location.id) - toast.success("Location Deleted", { - description: `${location.segment_name} has been removed from the system.`, + await chainageService.deleteChainage(chainage.id) + toast.success("Chainage Deleted", { + description: `${chainage.segment_name} has been removed from the system.`, }) - await loadLocations() + await loadChainages() } catch (err) { - setError("Failed to delete location") + setError("Failed to delete chainage") toast.error("Deletion Failed", { - description: "The location could not be removed. Please try again.", + description: "The chainage could not be removed. Please try again.", }) } finally { setIsLoading(false) @@ -259,14 +260,14 @@ export default function LocationPage() { return allPackages.find(p => p.id === packageId)?.name || packageId } - const isFormComplete = selectedPackageId && segmentName.trim() && startLat && startLng && endLat && endLng + const isFormComplete = selectedPackageId && segmentName.trim() && startLat && startLng && endLat && endLng && chainageStartKm && chainageEndKm - const columns: ColumnDef[] = [ + const columns: ColumnDef[] = [ { accessorKey: "segment_name", header: "Segment Name", cell: ({ row }) => ( -
{row.original.segment_name}
+
{row.original.segment_name}
) }, { @@ -278,8 +279,8 @@ export default function LocationPage() { accessorKey: "project", header: "Project", cell: ({ row }) => { - const location = row.original - const pkg = allPackages.find(p => p.id === location.package_id) + const chainage = row.original + const pkg = allPackages.find(p => p.id === chainage.package_id) const project = projects.find(p => p.id === pkg?.project_id) return project?.name || "—" } @@ -288,13 +289,13 @@ export default function LocationPage() { id: "project_state", header: "Project State", cell: ({ row }) => { - const location = row.original - const pkg = allPackages.find(p => p.id === location.package_id) + const chainage = row.original + const pkg = allPackages.find(p => p.id === chainage.package_id) const project = projects.find(p => p.id === pkg?.project_id) - if (!project?.state) return + if (!project?.state) return const states = project.state.split(',').map(s => s.trim()).filter(Boolean) - if (states.length === 0) return + if (states.length === 0) return const firstState = states[0] const remainingStates = states.slice(1) @@ -332,16 +333,25 @@ export default function LocationPage() { accessorKey: "chainage", header: "Chainage (km)", cell: ({ row }) => { - const location = row.original - if (location.chainage_start_km !== null && location.chainage_end_km !== null) { - return ( + const chainage = row.original + return ( +
- {location.chainage_start_km} - {location.chainage_end_km} + {chainage.chainage_start_km} - {chainage.chainage_end_km} - ) - } - return "—" + + {chainage.direction === "UP" ? ( + + ) : ( + + )} + {chainage.direction} + +
+ ) } }, { @@ -372,15 +382,15 @@ export default function LocationPage() { {/* Refined Header */}
{ setIsEditing(false); setIsModalOpen(true); }} > - - Add New Location + + Add New Chainage } /> @@ -390,8 +400,8 @@ export default function LocationPage() { {/* Data Table */}
- +
- {isEditing ? 'Edit Location Details' : 'Create New Location'} + {isEditing ? 'Edit Chainage Details' : 'Create New Chainage'}
- {isEditing ? 'Update the technical specifications for your road segment location.' : 'Select a project & package, then provide the essential location data.'} + {isEditing ? 'Update the technical specifications for your road segment chainage.' : 'Select a project & package, then provide the essential chainage data.'} @@ -494,14 +504,14 @@ export default function LocationPage() {
)} - {/* Step 3: Location Details */} + {/* Step 3: Chainage Details */}
{!isEditing && (
03
-

Location Information

+

Chainage Information

)} @@ -523,7 +533,7 @@ export default function LocationPage() {
setChainageStartKm(e.target.value)} placeholder="0.0" className="h-11 bg-muted/20 border-border/60 text-sm font-mono" + required />
setChainageEndKm(e.target.value)} placeholder="1.0" className="h-11 bg-muted/20 border-border/60 text-sm font-mono" + required />
+ {/* Direction Row */} +
+ + +
+ {/* GPS Coordinates Grid */}
{/* Start Point */} @@ -631,6 +663,7 @@ export default function LocationPage() { {/* Submit Button */}
diff --git a/src/app/(modules)/dashboard/page.tsx b/src/app/(modules)/dashboard/page.tsx index be08845..fc200a2 100644 --- a/src/app/(modules)/dashboard/page.tsx +++ b/src/app/(modules)/dashboard/page.tsx @@ -6,6 +6,7 @@ import { AlertCircle, Activity, MapPin as MapPinIcon, + Milestone, BarChart3, AlertTriangle, Zap, @@ -17,10 +18,10 @@ import { StatsCard } from "@/components/dashboard/stats-card"; import { CompactProjectSelector } from "@/components/dashboard/compact-project-selector"; import { FilterSelector } from "@/components/dashboard/filter-selector"; import { DetectionDonutChart } from "@/components/dashboard/detection-donut-chart"; -import { LocationBarChart } from "@/components/dashboard/location-bar-chart"; +import { ChainageBarChart } from "@/components/dashboard/chainage-bar-chart" import { DashboardMap } from "@/components/dashboard/dashboard-map"; import { PageHeader } from "@/components/page-header"; -import { fetchProjects } from "@/lib/api"; +import { projectService } from "@/services/api"; import { Project } from "@/types"; import { Popover, @@ -46,9 +47,9 @@ interface ProjectSummary { [key: string]: { package_id: string; region: string | null; - locations: { + chainages: { [key: string]: { - location_id: string; + chainage_id: string; chainage: string | null; detection_count: number; detections: Array<{ @@ -72,7 +73,7 @@ interface DetectionStats { totalDamagedRoadMarking: number; totalGoodSignboard: number; totalRoadDamage: number; - locationData: Array<{ + chainageData: Array<{ name: string; defected_sign_board: number; pothole: number; @@ -92,103 +93,93 @@ function calculateStats(summary: ProjectSummary | null): DetectionStats { totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalRoadDamage: 0, - locationData: [], + chainageData: [], }; } - let totalDefectedSignboard = 0; - let totalPothole = 0; - let totalRoadCrack = 0; - let totalDamagedRoadMarking = 0; - let totalGoodSignboard = 0; - let totalRoadDamage = 0; - const locationData: DetectionStats["locationData"] = []; + let total_defected_sign_board = 0; + let total_pothole = 0; + let total_road_crack = 0; + let total_damaged_road_marking = 0; + let total_good_sign_board = 0; + let total_road_damage = 0; + const chainageData: DetectionStats["chainageData"] = []; for (const pkg of Object.values(summary.packages || {})) { - for (const [locName, loc] of Object.entries(pkg.locations || {})) { - let locDefectedSignboard = 0; - let locPothole = 0; - let locRoadCrack = 0; - let locDamagedRoadMarking = 0; - let locGoodSignboard = 0; + for (const [chnName, chn] of Object.entries(pkg.chainages || {})) { // Changed 'locName, loc' to 'chnName, chn' and 'pkg.locations' to 'pkg.chainages' + let chnDefectedSignboard = 0; // Changed 'locDefectedSignboard' to 'chnDefectedSignboard' + let chnPothole = 0; // Changed 'locPothole' to 'chnPothole' + let chnRoadCrack = 0; // Changed 'locRoadCrack' to 'chnRoadCrack' + let chnDamagedRoadMarking = 0; // Changed 'locDamagedRoadMarking' to 'chnDamagedRoadMarking' + let chnGoodSignboard = 0; // Changed 'locGoodSignboard' to 'chnGoodSignboard' - for (const detection of loc.detections || []) { + for (const detection of chn.detections || []) { // Changed 'loc.detections' to 'chn.detections' const type = detection.type.toLowerCase(); if (type === "defected_sign_board") { - locDefectedSignboard++; - totalDefectedSignboard++; + chnDefectedSignboard++; + total_defected_sign_board++; } else if (type === "pothole") { - locPothole++; - totalPothole++; + chnPothole++; + total_pothole++; } else if (type === "road_crack") { - locRoadCrack++; - totalRoadCrack++; + chnRoadCrack++; + total_road_crack++; } else if (type === "damaged_road_marking") { - locDamagedRoadMarking++; - totalDamagedRoadMarking++; + chnDamagedRoadMarking++; + total_damaged_road_marking++; } else if (type === "good_sign_board") { - locGoodSignboard++; - totalGoodSignboard++; + chnGoodSignboard++; + total_good_sign_board++; } } - const locTotalDamage = - locDefectedSignboard + - locPothole + - locRoadCrack + - locDamagedRoadMarking; - totalRoadDamage += - locDefectedSignboard > 0 || - locPothole > 0 || - locRoadCrack > 0 || - locDamagedRoadMarking > 0 - ? 1 - : 0; // This logic might need refinement based on "total_road_damage" definition - + const chnTotalDamage = // Changed 'locTotalDamage' to 'chnTotalDamage' + chnDefectedSignboard + + chnPothole + + chnRoadCrack + + chnDamagedRoadMarking; + if ( - locDefectedSignboard > 0 || - locPothole > 0 || - locRoadCrack > 0 || - locDamagedRoadMarking > 0 || - locGoodSignboard > 0 + chnDefectedSignboard > 0 || + chnPothole > 0 || + chnRoadCrack > 0 || + chnDamagedRoadMarking > 0 || + chnGoodSignboard > 0 ) { const shortName = - locName.length > 20 ? locName.substring(0, 20) + "..." : locName; - locationData.push({ + chnName.length > 20 ? chnName.substring(0, 20) + "..." : chnName; + chainageData.push({ // Changed 'locationData.push' to 'chainageData.push' name: shortName, - defected_sign_board: locDefectedSignboard, - pothole: locPothole, - road_crack: locRoadCrack, - damaged_road_marking: locDamagedRoadMarking, - good_sign_board: locGoodSignboard, + defected_sign_board: chnDefectedSignboard, + pothole: chnPothole, + road_crack: chnRoadCrack, + damaged_road_marking: chnDamagedRoadMarking, + good_sign_board: chnGoodSignboard, total: - locDefectedSignboard + - locPothole + - locRoadCrack + - locDamagedRoadMarking + - locGoodSignboard, + chnDefectedSignboard + + chnPothole + + chnRoadCrack + + chnDamagedRoadMarking + + chnGoodSignboard, }); } } } - // Recalculate totalRoadDamage based on backends unique count - // But since we are aggregating from locations, we just sum them up or use a simpler metric - // The user's JSON shows "total_road_damage": 9 which is sum of 2+6+0+1 - totalRoadDamage = - totalDefectedSignboard + - totalPothole + - totalRoadCrack + - totalDamagedRoadMarking; + total_road_damage = + total_defected_sign_board + + total_pothole + + total_road_crack + + total_damaged_road_marking; return { - totalDefectedSignboard, - totalPothole, - totalRoadCrack, - totalDamagedRoadMarking, - totalGoodSignboard, - totalRoadDamage, - locationData, + totalDefectedSignboard: total_defected_sign_board, + totalPothole: total_pothole, + totalRoadCrack: total_road_crack, + totalDamagedRoadMarking: total_damaged_road_marking, + totalGoodSignboard: total_good_sign_board, + totalRoadDamage: total_road_damage, + chainageData, }; } @@ -208,7 +199,7 @@ export default function DashboardPage() { totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalRoadDamage: 0, - locationData: [], + chainageData: [], }); // Load projects on mount @@ -216,7 +207,7 @@ export default function DashboardPage() { const loadProjects = async () => { try { setIsLoading(true); - const projectsData = await fetchProjects(); + const projectsData = await projectService.getProjects(); setProjects(projectsData.items); if (projectsData.items.length > 0) { @@ -247,21 +238,21 @@ export default function DashboardPage() { })) : []; - // Extract locations from selected package + // Extract chainages from selected package const [selectedPackageId, setSelectedPackageId] = useState( null, ); - const [selectedLocationId, setSelectedLocationId] = useState( + const [selectedChainageId, setSelectedChainageId] = useState( // Changed 'selectedLocationId' to 'selectedChainageId' null, ); - const locations = + const chainages = projectSummary && selectedPackageId && selectedPackageId !== "all" ? Object.keys( - projectSummary.packages[selectedPackageId]?.locations || {}, - ).map((locName) => ({ - id: locName, - name: locName, + projectSummary.packages[selectedPackageId]?.chainages || {}, + ).map((chnName) => ({ + id: chnName, + name: chnName, })) : []; @@ -276,21 +267,7 @@ export default function DashboardPage() { try { setIsLoading(true); - const response = await fetch( - `${API_URL}/summary/projects/${selectedProjectId}`, - { - headers: { - "Content-Type": "application/json", - "ngrok-skip-browser-warning": "true", - }, - }, - ); - - if (!response.ok) { - throw new Error(`API Error: ${response.status}`); - } - - const summary: ProjectSummary = await response.json(); + const summary: ProjectSummary = await projectService.getProjectSummary(selectedProjectId); setProjectSummary(summary); } catch (err) { console.error("Failed to load project summary:", err); @@ -306,15 +283,15 @@ export default function DashboardPage() { loadProjectSummary(); }, [selectedProjectId]); - // Reset package and location when project changes + // Reset package and chainage when project changes useEffect(() => { setSelectedPackageId(null); - setSelectedLocationId(null); + setSelectedChainageId(null); // Changed 'setSelectedLocationId' to 'setSelectedChainageId' }, [selectedProjectId]); - // Reset location when package changes + // Reset chainage when package changes useEffect(() => { - setSelectedLocationId(null); + setSelectedChainageId(null); }, [selectedPackageId]); // Filter stats based on selections @@ -327,7 +304,7 @@ export default function DashboardPage() { totalDamagedRoadMarking: 0, totalGoodSignboard: 0, totalRoadDamage: 0, - locationData: [], + chainageData: [], // Changed 'locationData' to 'chainageData' }); return; } @@ -337,7 +314,7 @@ export default function DashboardPage() { let totalRoadCrack = 0; let totalDamagedRoadMarking = 0; let totalGoodSignboard = 0; - const locationData: DetectionStats["locationData"] = []; + const chainageData: DetectionStats["chainageData"] = []; const packagesToProcess = selectedPackageId && selectedPackageId !== "all" @@ -345,62 +322,62 @@ export default function DashboardPage() { : projectSummary.packages || {}; for (const [pkgName, pkg] of Object.entries(packagesToProcess)) { - const locationsToProcess = - selectedLocationId && selectedLocationId !== "all" - ? { [selectedLocationId]: pkg.locations[selectedLocationId] } - : pkg.locations || {}; + const chainagesToProcess = + selectedChainageId && selectedChainageId !== "all" + ? { [selectedChainageId]: pkg.chainages[selectedChainageId] } + : pkg.chainages || {}; - for (const [locName, loc] of Object.entries(locationsToProcess)) { - if (!loc) continue; + for (const [chnName, chn] of Object.entries(chainagesToProcess)) { + if (!chn) continue; - let locDefectedSignboard = 0; - let locPothole = 0; - let locRoadCrack = 0; - let locDamagedRoadMarking = 0; - let locGoodSignboard = 0; + let chnDefectedSignboard = 0; + let chnPothole = 0; + let chnRoadCrack = 0; + let chnDamagedRoadMarking = 0; + let chnGoodSignboard = 0; - for (const detection of loc.detections || []) { + for (const detection of chn.detections || []) { const type = detection.type.toLowerCase(); if (type === "defected_sign_board") { - locDefectedSignboard++; + chnDefectedSignboard++; totalDefectedSignboard++; } else if (type === "pothole") { - locPothole++; + chnPothole++; totalPothole++; } else if (type === "road_crack") { - locRoadCrack++; + chnRoadCrack++; totalRoadCrack++; } else if (type === "damaged_road_marking") { - locDamagedRoadMarking++; + chnDamagedRoadMarking++; totalDamagedRoadMarking++; } else if (type === "good_sign_board") { - locGoodSignboard++; + chnGoodSignboard++; totalGoodSignboard++; } } if ( - locDefectedSignboard > 0 || - locPothole > 0 || - locRoadCrack > 0 || - locDamagedRoadMarking > 0 || - locGoodSignboard > 0 + chnDefectedSignboard > 0 || + chnPothole > 0 || + chnRoadCrack > 0 || + chnDamagedRoadMarking > 0 || + chnGoodSignboard > 0 ) { const shortName = - locName.length > 20 ? locName.substring(0, 20) + "..." : locName; - locationData.push({ + chnName.length > 20 ? chnName.substring(0, 20) + "..." : chnName; + chainageData.push({ name: shortName, - defected_sign_board: locDefectedSignboard, - pothole: locPothole, - road_crack: locRoadCrack, - damaged_road_marking: locDamagedRoadMarking, - good_sign_board: locGoodSignboard, + defected_sign_board: chnDefectedSignboard, + pothole: chnPothole, + road_crack: chnRoadCrack, + damaged_road_marking: chnDamagedRoadMarking, + good_sign_board: chnGoodSignboard, total: - locDefectedSignboard + - locPothole + - locRoadCrack + - locDamagedRoadMarking + - locGoodSignboard, + chnDefectedSignboard + + chnPothole + + chnRoadCrack + + chnDamagedRoadMarking + + chnGoodSignboard, }); } } @@ -417,9 +394,9 @@ export default function DashboardPage() { totalPothole + totalRoadCrack + totalDamagedRoadMarking, - locationData, + chainageData, }); - }, [projectSummary, selectedPackageId, selectedLocationId]); + }, [projectSummary, selectedPackageId, selectedChainageId]); return ( <> @@ -445,22 +422,22 @@ export default function DashboardPage() {

- + Filter Analysis

-

Refine your view by project, package, or location

+

Refine your view by project, package, or chainage

@@ -554,19 +531,13 @@ export default function DashboardPage() { - Detections by Location - Frequency of road issues across different map segments + Severity by Chainage + Detections grouped by road segment - - ({ - name: loc.name, - defected_sign_board: loc.defected_sign_board, - pothole: loc.pothole, - road_crack: loc.road_crack, - damaged_road_marking: loc.damaged_road_marking, - total: loc.total, - }))} + + @@ -581,10 +552,10 @@ export default function DashboardPage() { {/* Map - Full Width Below Charts */}
-
diff --git a/src/app/(modules)/new-analysis/page.tsx b/src/app/(modules)/new-analysis/page.tsx index 1da699e..ee9ac82 100644 --- a/src/app/(modules)/new-analysis/page.tsx +++ b/src/app/(modules)/new-analysis/page.tsx @@ -2,7 +2,7 @@ import { useRouter } from "next/navigation" import dynamic from "next/dynamic" -import { saveSession } from "@/lib/api" +import { sessionService } from "@/services/api" import { SessionContext } from "@/types" import { PowerCircle, TrendingUp } from "lucide-react" import { PageHeader } from "@/components/page-header" @@ -21,7 +21,7 @@ export default function NewAnalysisPage() { const handleSelectionComplete = (session: SessionContext) => { // Save session to storage and navigate to upload page - saveSession(session) + sessionService.saveSession(session) router.push(ROUTES.UPLOAD) } diff --git a/src/app/(modules)/package/page.tsx b/src/app/(modules)/package/page.tsx index 9bd6bcf..77fbd8e 100644 --- a/src/app/(modules)/package/page.tsx +++ b/src/app/(modules)/package/page.tsx @@ -10,13 +10,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f import { Loader2, CheckCircle2, Package, FolderKanban, Globe } from "lucide-react" import { DataTable } from "@/components/data-table" import { PageHeader } from "@/components/page-header" -import { - fetchProjects, - fetchAllPackages, - createPackage, - updatePackage, - deletePackage -} from "@/lib/api" +import { projectService, packageService } from "@/services/api" import { Project, Package as PackageType, @@ -51,13 +45,15 @@ export default function PackagePage() { const [selectedProjectId, setSelectedProjectId] = useState("") const [name, setName] = useState("") const [region, setRegion] = useState("") + const [chainageStartKm, setChainageStartKm] = useState("") + const [chainageEndKm, setChainageEndKm] = useState("") // Load packages and projects const loadPackages = async (currentSkip = skip, currentLimit = limit) => { try { setIsLoading(true) setError(null) - const data = await fetchAllPackages({ skip: currentSkip, limit: currentLimit }) + const data = await packageService.getPackages({ skip: currentSkip, limit: currentLimit }) setPackages(data.items) setTotalItems(data.totalItems) } catch (err) { @@ -73,7 +69,7 @@ export default function PackagePage() { const loadProjects = async () => { try { setLoadingProjects(true) - const data = await fetchProjects({ skip: 0, limit: 1000 }) // Load all projects for selector + const data = await projectService.getProjects({ skip: 0, limit: 1000 }) // Load all projects for selector setProjects(data.items) } catch (err) { setError("Failed to load projects.") @@ -91,6 +87,8 @@ export default function PackagePage() { setSelectedProjectId("") setName("") setRegion("") + setChainageStartKm("") + setChainageEndKm("") setError(null) setIsEditing(false) setCurrentPackage(null) @@ -115,8 +113,10 @@ export default function PackagePage() { const data: PackageUpdate = { name: name.trim(), region: region.trim() || null, + chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : 0, + chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : 0, } - await updatePackage(currentPackage.id, data) + await packageService.updatePackage(currentPackage.id, data) toast.success("Package Updated", { description: `${name} has been updated successfully at ${new Date().toLocaleTimeString()}`, }) @@ -125,8 +125,10 @@ export default function PackagePage() { project_id: selectedProjectId, name: name.trim(), region: region.trim() || null, + chainage_start_km: chainageStartKm ? parseFloat(chainageStartKm) : 0, + chainage_end_km: chainageEndKm ? parseFloat(chainageEndKm) : 0, } - await createPackage(data) + await packageService.createPackage(data) toast.success("Package Created", { description: `${name} has been established successfully at ${new Date().toLocaleTimeString()}`, }) @@ -155,6 +157,8 @@ export default function PackagePage() { setSelectedProjectId(pkg.project_id) setName(pkg.name || "") setRegion(pkg.region || "") + setChainageStartKm(pkg.chainage_start_km?.toString() || "0") + setChainageEndKm(pkg.chainage_end_km?.toString() || "0") setIsModalOpen(true) } @@ -163,7 +167,7 @@ export default function PackagePage() { try { setIsLoading(true) - await deletePackage(pkg.id) + await packageService.deletePackage(pkg.id) toast.success("Package Deleted", { description: `${pkg.name} has been removed from the system.`, }) @@ -242,6 +246,16 @@ export default function PackagePage() { accessorKey: "region", header: "Region", }, + { + accessorKey: "chainage_start_km", + header: "Start (km)", + cell: ({ row }) => row.original.chainage_start_km?.toFixed(2) ?? "0.00" + }, + { + accessorKey: "chainage_end_km", + header: "End (km)", + cell: ({ row }) => row.original.chainage_end_km?.toFixed(2) ?? "0.00" + }, ] return ( @@ -383,6 +397,36 @@ export default function PackagePage() { className="h-11 bg-muted/20 border-border/60" />
+ +
+ + setChainageStartKm(e.target.value)} + placeholder="0.00" + className="h-11 bg-muted/20 border-border/60" + /> +
+ +
+ + setChainageEndKm(e.target.value)} + placeholder="0.00" + className="h-11 bg-muted/20 border-border/60" + /> +
@@ -396,14 +440,14 @@ export default function PackagePage() { resetForm() }} disabled={isSubmitting} - className="flex-1 h-12 font-bold uppercase tracking-wider text-xs border-border/80 hover:bg-muted/50" + className="flex-1" > Cancel