feat(api): restructure api with axios and chainage related changes

This commit is contained in:
2026-03-18 18:42:40 +05:30
parent 7f8854ed78
commit 598098d7e8
31 changed files with 933 additions and 931 deletions

View File

@@ -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<string | null>(
null,
);
const [selectedLocationId, setSelectedLocationId] = useState<string | null>(
const [selectedChainageId, setSelectedChainageId] = useState<string | null>( // 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() {
<PopoverContent className="w-[320px] p-0 overflow-hidden" align="end">
<div className="p-4 border-b bg-muted/30">
<h3 className="font-bold text-sm flex items-center gap-2 text-foreground">
<Settings2 className="h-4 w-4 text-primary" />
<Milestone className="h-4 w-4 text-primary" />
Filter Analysis
</h3>
<p className="text-xs mt-1 text-muted-foreground">Refine your view by project, package, or location</p>
<p className="text-xs mt-1 text-muted-foreground">Refine your view by project, package, or chainage</p>
</div>
<div>
<FilterSelector
projects={projects}
selectedProjectId={selectedProjectId}
selectedPackageId={selectedPackageId}
selectedLocationId={selectedLocationId}
selectedChainageId={selectedChainageId} // Changed 'selectedLocationId' to 'selectedChainageId'
onProjectChange={setSelectedProjectId}
onPackageChange={setSelectedPackageId}
onLocationChange={setSelectedLocationId}
onChainageChange={setSelectedChainageId} // Changed 'onLocationChange' to 'onChainageChange'
packages={packages}
locations={locations}
chainages={chainages} // Changed 'locations' to 'chainages'
isLoading={isLoading}
/>
</div>
@@ -554,19 +531,13 @@ export default function DashboardPage() {
<Reveal delay={0.3} direction="right" className="flex flex-col">
<Card className="flex-1 h-full">
<CardHeader className="items-center pb-0">
<CardTitle>Detections by Location</CardTitle>
<CardDescription>Frequency of road issues across different map segments</CardDescription>
<CardTitle className="text-base font-bold">Severity by Chainage</CardTitle>
<CardDescription className="text-xs">Detections grouped by road segment</CardDescription>
</CardHeader>
<CardContent className="flex-1 pb-0">
<LocationBarChart
data={stats.locationData.map((loc) => ({
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,
}))}
<CardContent className="h-[300px]">
<ChainageBarChart
data={stats.chainageData || []}
isLoading={isLoading}
/>
</CardContent>
<CardFooter className="flex-col gap-2 text-sm">
@@ -581,10 +552,10 @@ export default function DashboardPage() {
{/* Map - Full Width Below Charts */}
<Reveal delay={0.4} direction="up">
<div>
<DashboardMap
<DashboardMap
selectedProjectId={selectedProjectId}
selectedPackageId={selectedPackageId}
selectedLocationId={selectedLocationId}
selectedChainageId={selectedChainageId} // Changed 'selectedLocationId' to 'selectedChainageId'
projectSummary={projectSummary}
/>
</div>