refactor: remove static routes and stop multiple token api call in sse
This commit is contained in:
@@ -1,678 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
} from '@/components/ui/card';
|
||||
import {
|
||||
AlertCircle,
|
||||
Activity,
|
||||
MapPin as MapPinIcon,
|
||||
Milestone,
|
||||
BarChart3,
|
||||
AlertTriangle,
|
||||
Zap,
|
||||
PencilLine,
|
||||
CheckCircle2,
|
||||
Settings2,
|
||||
Droplets,
|
||||
} from 'lucide-react';
|
||||
import { StatsCard } from '@/components/dashboard/stats-card';
|
||||
import { FilterSelector } from '@/components/dashboard/filter-selector';
|
||||
import { DetectionDonutChart } from '@/components/dashboard/detection-donut-chart';
|
||||
import { ChainageBarChart } from '@/components/dashboard/chainage-bar-chart';
|
||||
import { DashboardMap } from '@/components/dashboard/dashboard-map';
|
||||
import { PageHeader } from '@/components/page-header';
|
||||
import { projectService, projectSummaryService } from '@/services/api';
|
||||
import { Project } from '@/types';
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DashboardSkeleton } from '@/components/dashboard/dashboard-skeleton';
|
||||
import { toast } from 'sonner';
|
||||
import { Reveal, StaggerContainer, StaggerItem } from '@/components/ui/reveal';
|
||||
|
||||
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||
|
||||
interface ProjectSummary {
|
||||
project: {
|
||||
id: string;
|
||||
name: string;
|
||||
corridor_name: string | null;
|
||||
state: string | null;
|
||||
};
|
||||
packages: {
|
||||
[key: string]: {
|
||||
package_id: string;
|
||||
region: string | null;
|
||||
chainages: {
|
||||
[key: string]: {
|
||||
chainage_id: string;
|
||||
chainage: string | null;
|
||||
detection_count: number;
|
||||
detections: Array<{
|
||||
id: number;
|
||||
type: string;
|
||||
class: string;
|
||||
confidence: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface DetectionStats {
|
||||
totalDefectedSignboard: number;
|
||||
totalPothole: number;
|
||||
totalRoadCrack: number;
|
||||
totalDamagedRoadMarking: number;
|
||||
totalGoodSignboard: number;
|
||||
totalDrainIssue: number;
|
||||
totalDefectiveCulvert: number;
|
||||
totalRoadDamage: number;
|
||||
chainageData: Array<{
|
||||
name: string;
|
||||
defected_sign_board: number;
|
||||
pothole: number;
|
||||
road_crack: number;
|
||||
damaged_road_marking: number;
|
||||
// good_sign_board: number;
|
||||
drain_issue: number;
|
||||
defective_culvert: number;
|
||||
total: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
function calculateStats(summary: ProjectSummary | null): DetectionStats {
|
||||
if (!summary) {
|
||||
return {
|
||||
totalDefectedSignboard: 0,
|
||||
totalPothole: 0,
|
||||
totalRoadCrack: 0,
|
||||
totalDamagedRoadMarking: 0,
|
||||
totalGoodSignboard: 0,
|
||||
totalDrainIssue: 0,
|
||||
totalDefectiveCulvert: 0,
|
||||
totalRoadDamage: 0,
|
||||
chainageData: [],
|
||||
};
|
||||
}
|
||||
|
||||
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_drain_issue = 0;
|
||||
let total_defective_culvert = 0;
|
||||
let total_road_damage = 0;
|
||||
const chainageData: DetectionStats['chainageData'] = [];
|
||||
|
||||
for (const pkg of Object.values(summary.packages || {})) {
|
||||
for (const [chnName, chn] of Object.entries(pkg.chainages || {})) {
|
||||
let chnDefectedSignboard = 0;
|
||||
let chnPothole = 0;
|
||||
let chnRoadCrack = 0;
|
||||
let chnDamagedRoadMarking = 0;
|
||||
let chnGoodSignboard = 0;
|
||||
let chnDrainIssue = 0;
|
||||
let chnDefectiveCulvert = 0;
|
||||
|
||||
for (const detection of chn.detections || []) {
|
||||
const type = (detection.type || '').toLowerCase();
|
||||
if (type === 'defected_sign_board') {
|
||||
chnDefectedSignboard++;
|
||||
total_defected_sign_board++;
|
||||
} else if (type === 'pothole') {
|
||||
chnPothole++;
|
||||
total_pothole++;
|
||||
} else if (type === 'road_crack') {
|
||||
chnRoadCrack++;
|
||||
total_road_crack++;
|
||||
} else if (type === 'damaged_road_marking') {
|
||||
chnDamagedRoadMarking++;
|
||||
total_damaged_road_marking++;
|
||||
} else if (type === 'good_sign_board') {
|
||||
chnGoodSignboard++;
|
||||
// total_good_sign_board++;
|
||||
} else if (type === 'drain_issue') {
|
||||
chnDrainIssue++;
|
||||
total_drain_issue++;
|
||||
} else if (type === 'defective_culvert') {
|
||||
chnDefectiveCulvert++;
|
||||
total_defective_culvert++;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
chnDefectedSignboard > 0 ||
|
||||
chnPothole > 0 ||
|
||||
chnRoadCrack > 0 ||
|
||||
chnDamagedRoadMarking > 0 ||
|
||||
chnGoodSignboard > 0 ||
|
||||
chnDrainIssue > 0 ||
|
||||
chnDefectiveCulvert > 0
|
||||
) {
|
||||
const shortName =
|
||||
chnName.length > 20 ? chnName.substring(0, 20) + '...' : chnName;
|
||||
chainageData.push({
|
||||
name: shortName,
|
||||
defected_sign_board: chnDefectedSignboard,
|
||||
pothole: chnPothole,
|
||||
road_crack: chnRoadCrack,
|
||||
damaged_road_marking: chnDamagedRoadMarking,
|
||||
// good_sign_board: chnGoodSignboard,
|
||||
drain_issue: chnDrainIssue,
|
||||
defective_culvert: chnDefectiveCulvert,
|
||||
total:
|
||||
chnDefectedSignboard +
|
||||
chnPothole +
|
||||
chnRoadCrack +
|
||||
chnDamagedRoadMarking +
|
||||
// chnGoodSignboard +
|
||||
chnDrainIssue +
|
||||
chnDefectiveCulvert,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total_road_damage =
|
||||
total_defected_sign_board +
|
||||
total_pothole +
|
||||
total_road_crack +
|
||||
total_damaged_road_marking +
|
||||
total_drain_issue +
|
||||
total_defective_culvert;
|
||||
|
||||
return {
|
||||
totalDefectedSignboard: total_defected_sign_board,
|
||||
totalPothole: total_pothole,
|
||||
totalRoadCrack: total_road_crack,
|
||||
totalDamagedRoadMarking: total_damaged_road_marking,
|
||||
totalGoodSignboard: total_good_sign_board,
|
||||
totalDrainIssue: total_drain_issue,
|
||||
totalDefectiveCulvert: total_defective_culvert,
|
||||
totalRoadDamage: total_road_damage,
|
||||
chainageData,
|
||||
};
|
||||
}
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [projects, setProjects] = useState<Project[]>([]);
|
||||
const [selectedProjectId, setSelectedProjectId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [projectSummary, setProjectSummary] = useState<ProjectSummary | null>(
|
||||
null,
|
||||
);
|
||||
const [stats, setStats] = useState<DetectionStats>({
|
||||
totalDefectedSignboard: 0,
|
||||
totalPothole: 0,
|
||||
totalRoadCrack: 0,
|
||||
totalDamagedRoadMarking: 0,
|
||||
totalGoodSignboard: 0,
|
||||
totalDrainIssue: 0,
|
||||
totalDefectiveCulvert: 0,
|
||||
totalRoadDamage: 0,
|
||||
chainageData: [],
|
||||
});
|
||||
|
||||
// Load projects on mount
|
||||
useEffect(() => {
|
||||
const loadProjects = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const projectsData = await projectService.getProjects();
|
||||
setProjects(projectsData.items);
|
||||
|
||||
if (projectsData.items.length > 0) {
|
||||
setSelectedProjectId(
|
||||
projectsData.items[projectsData.items.length - 1].id,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to load projects:', err);
|
||||
toast.error('Failed to load projects', {
|
||||
description: 'Please check if the backend is running.',
|
||||
});
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
loadProjects();
|
||||
}, []);
|
||||
|
||||
const selectedProject = projects.find((p) => p.id === selectedProjectId);
|
||||
|
||||
// Extract packages from project summary
|
||||
const packages = projectSummary
|
||||
? Object.keys(projectSummary.packages || {}).map((pkgName) => ({
|
||||
id: pkgName,
|
||||
name: pkgName,
|
||||
}))
|
||||
: [];
|
||||
|
||||
// Extract chainages from selected package
|
||||
const [selectedPackageId, setSelectedPackageId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [selectedChainageId, setSelectedChainageId] = useState<string | null>(
|
||||
null,
|
||||
); // Changed 'selectedLocationId' to 'selectedChainageId'
|
||||
|
||||
const chainages =
|
||||
projectSummary && selectedPackageId && selectedPackageId !== 'all'
|
||||
? Object.keys(
|
||||
projectSummary.packages[selectedPackageId]?.chainages || {},
|
||||
).map((chnName) => ({
|
||||
id: chnName,
|
||||
name: chnName,
|
||||
}))
|
||||
: [];
|
||||
|
||||
// Load project summary when project changes
|
||||
useEffect(() => {
|
||||
if (!selectedProjectId) {
|
||||
setProjectSummary(null);
|
||||
return;
|
||||
}
|
||||
setProjectSummary(null);
|
||||
|
||||
const loadProjectSummary = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const summary =
|
||||
await projectSummaryService.getProjectSummary<ProjectSummary>(
|
||||
selectedProjectId,
|
||||
);
|
||||
setProjectSummary(summary);
|
||||
} catch (err) {
|
||||
console.error('Failed to load project summary:', err);
|
||||
toast.error('Failed to load project summary');
|
||||
setProjectSummary(null);
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
loadProjectSummary();
|
||||
}, [selectedProjectId]);
|
||||
|
||||
// Reset package and chainage when project changes
|
||||
useEffect(() => {
|
||||
setSelectedPackageId(null);
|
||||
setSelectedChainageId(null);
|
||||
}, [selectedProjectId]);
|
||||
|
||||
// Auto-select last package when project summary is loaded
|
||||
useEffect(() => {
|
||||
if (projectSummary && selectedPackageId === null) {
|
||||
const packageIds = Object.keys(projectSummary.packages || {});
|
||||
if (packageIds.length > 0) {
|
||||
setSelectedPackageId(packageIds[packageIds.length - 1]);
|
||||
}
|
||||
}
|
||||
}, [projectSummary, selectedPackageId]);
|
||||
|
||||
// Reset chainage when package changes
|
||||
useEffect(() => {
|
||||
setSelectedChainageId(null);
|
||||
}, [selectedPackageId]);
|
||||
|
||||
// Auto-select last chainage when package is selected
|
||||
useEffect(() => {
|
||||
if (
|
||||
projectSummary &&
|
||||
selectedPackageId &&
|
||||
selectedPackageId !== 'all' &&
|
||||
selectedChainageId === null
|
||||
) {
|
||||
const chainageIds = Object.keys(
|
||||
projectSummary.packages[selectedPackageId]?.chainages || {},
|
||||
);
|
||||
if (chainageIds.length > 0) {
|
||||
setSelectedChainageId(chainageIds[chainageIds.length - 1]);
|
||||
}
|
||||
}
|
||||
}, [projectSummary, selectedPackageId, selectedChainageId]);
|
||||
|
||||
// Filter stats based on selections
|
||||
useEffect(() => {
|
||||
if (!projectSummary) {
|
||||
setStats({
|
||||
totalDefectedSignboard: 0,
|
||||
totalPothole: 0,
|
||||
totalRoadCrack: 0,
|
||||
totalDamagedRoadMarking: 0,
|
||||
totalGoodSignboard: 0,
|
||||
totalDrainIssue: 0,
|
||||
totalDefectiveCulvert: 0,
|
||||
totalRoadDamage: 0,
|
||||
chainageData: [], // Changed 'locationData' to 'chainageData'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let totalDefectedSignboard = 0;
|
||||
let totalPothole = 0;
|
||||
let totalRoadCrack = 0;
|
||||
let totalDamagedRoadMarking = 0;
|
||||
let totalGoodSignboard = 0;
|
||||
let totalDrainIssue = 0;
|
||||
let totalDefectiveCulvert = 0;
|
||||
const chainageData: DetectionStats['chainageData'] = [];
|
||||
|
||||
const packagesToProcess =
|
||||
selectedPackageId && selectedPackageId !== 'all'
|
||||
? { [selectedPackageId]: projectSummary.packages[selectedPackageId] }
|
||||
: projectSummary.packages || {};
|
||||
|
||||
for (const [pkgName, pkg] of Object.entries(packagesToProcess)) {
|
||||
if (!pkg) continue;
|
||||
|
||||
const chainagesToProcess =
|
||||
selectedChainageId && selectedChainageId !== 'all'
|
||||
? { [selectedChainageId]: pkg.chainages?.[selectedChainageId] }
|
||||
: pkg.chainages || {};
|
||||
|
||||
for (const [chnName, chn] of Object.entries(chainagesToProcess)) {
|
||||
if (!chn) continue;
|
||||
|
||||
let chnDefectedSignboard = 0;
|
||||
let chnPothole = 0;
|
||||
let chnRoadCrack = 0;
|
||||
let chnDamagedRoadMarking = 0;
|
||||
let chnGoodSignboard = 0;
|
||||
let chnDrainIssue = 0;
|
||||
let chnDefectiveCulvert = 0;
|
||||
|
||||
for (const detection of chn.detections || []) {
|
||||
const type = (detection.type || '').toLowerCase();
|
||||
if (type === 'defected_sign_board') {
|
||||
chnDefectedSignboard++;
|
||||
totalDefectedSignboard++;
|
||||
} else if (type === 'pothole') {
|
||||
chnPothole++;
|
||||
totalPothole++;
|
||||
} else if (type === 'road_crack') {
|
||||
chnRoadCrack++;
|
||||
totalRoadCrack++;
|
||||
} else if (type === 'damaged_road_marking') {
|
||||
chnDamagedRoadMarking++;
|
||||
totalDamagedRoadMarking++;
|
||||
} else if (type === 'good_sign_board') {
|
||||
// chnGoodSignboard++;
|
||||
// totalGoodSignboard++;
|
||||
} else if (type === 'drain_issue') {
|
||||
chnDrainIssue++;
|
||||
totalDrainIssue++;
|
||||
} else if (type === 'defective_culvert') {
|
||||
chnDefectiveCulvert++;
|
||||
totalDefectiveCulvert++;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
chnDefectedSignboard > 0 ||
|
||||
chnPothole > 0 ||
|
||||
chnRoadCrack > 0 ||
|
||||
chnDamagedRoadMarking > 0 ||
|
||||
chnGoodSignboard > 0 ||
|
||||
chnDrainIssue > 0 ||
|
||||
chnDefectiveCulvert > 0
|
||||
) {
|
||||
const shortName =
|
||||
chnName.length > 20 ? chnName.substring(0, 20) + '...' : chnName;
|
||||
chainageData.push({
|
||||
name: shortName,
|
||||
defected_sign_board: chnDefectedSignboard,
|
||||
pothole: chnPothole,
|
||||
road_crack: chnRoadCrack,
|
||||
damaged_road_marking: chnDamagedRoadMarking,
|
||||
// good_sign_board: chnGoodSignboard,
|
||||
drain_issue: chnDrainIssue,
|
||||
defective_culvert: chnDefectiveCulvert,
|
||||
total:
|
||||
chnDefectedSignboard +
|
||||
chnPothole +
|
||||
chnRoadCrack +
|
||||
chnDamagedRoadMarking +
|
||||
// chnGoodSignboard +
|
||||
chnDrainIssue +
|
||||
chnDefectiveCulvert,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setStats({
|
||||
totalDefectedSignboard,
|
||||
totalPothole,
|
||||
totalRoadCrack,
|
||||
totalDamagedRoadMarking,
|
||||
totalGoodSignboard,
|
||||
totalDrainIssue,
|
||||
totalDefectiveCulvert,
|
||||
totalRoadDamage:
|
||||
totalDefectedSignboard +
|
||||
totalPothole +
|
||||
totalRoadCrack +
|
||||
totalDamagedRoadMarking +
|
||||
totalDrainIssue +
|
||||
totalDefectiveCulvert,
|
||||
chainageData,
|
||||
});
|
||||
}, [projectSummary, selectedPackageId, selectedChainageId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Main Content */}
|
||||
<main>
|
||||
<div>
|
||||
{/* Header */}
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<PageHeader
|
||||
title="Dashboard"
|
||||
description="A Comprehensive Overview Of Your Road Infrastructure Analysis"
|
||||
icon={BarChart3}
|
||||
/>
|
||||
|
||||
<Reveal direction="down" delay={0.2}>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="gap-2 font-semibold">
|
||||
<Settings2 className="h-4 w-4" />
|
||||
<span>Filters</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<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">
|
||||
<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 segment
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<FilterSelector
|
||||
projects={projects}
|
||||
selectedProjectId={selectedProjectId}
|
||||
selectedPackageId={selectedPackageId}
|
||||
selectedChainageId={selectedChainageId} // Changed 'selectedLocationId' to 'selectedChainageId'
|
||||
onProjectChange={setSelectedProjectId}
|
||||
onPackageChange={setSelectedPackageId}
|
||||
onChainageChange={setSelectedChainageId} // Changed 'onLocationChange' to 'onChainageChange'
|
||||
packages={packages}
|
||||
chainages={chainages} // Changed 'locations' to 'chainages'
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
{isLoading && !projectSummary ? (
|
||||
<DashboardSkeleton />
|
||||
) : (
|
||||
<>
|
||||
{/* Stats Cards - Top Row */}
|
||||
<StaggerContainer className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
|
||||
<StaggerItem>
|
||||
<StatsCard
|
||||
title="Total Road Damage"
|
||||
subtitle="Combined damage detections"
|
||||
value={stats.totalRoadDamage}
|
||||
icon={Activity}
|
||||
/>
|
||||
</StaggerItem>
|
||||
<StaggerItem>
|
||||
<StatsCard
|
||||
title="Potholes"
|
||||
subtitle="Surface depressions"
|
||||
value={stats.totalPothole}
|
||||
icon={AlertCircle}
|
||||
/>
|
||||
</StaggerItem>
|
||||
<StaggerItem>
|
||||
<StatsCard
|
||||
title="Defected Signboards"
|
||||
subtitle="Damaged traffic signs"
|
||||
value={stats.totalDefectedSignboard}
|
||||
icon={AlertTriangle}
|
||||
/>
|
||||
</StaggerItem>
|
||||
<StaggerItem>
|
||||
<StatsCard
|
||||
title="Road Cracks"
|
||||
subtitle="Surface fissures"
|
||||
value={stats.totalRoadCrack}
|
||||
icon={Zap}
|
||||
/>
|
||||
</StaggerItem>
|
||||
<StaggerItem>
|
||||
<StatsCard
|
||||
title="Damaged Markings"
|
||||
subtitle="Worn road lines"
|
||||
value={stats.totalDamagedRoadMarking}
|
||||
icon={PencilLine}
|
||||
/>
|
||||
</StaggerItem>
|
||||
<StaggerItem>
|
||||
<StatsCard
|
||||
title="Drain Issues"
|
||||
subtitle="Clogged or broken drainage"
|
||||
value={stats.totalDrainIssue}
|
||||
icon={Droplets}
|
||||
/>
|
||||
</StaggerItem>
|
||||
{/* <StaggerItem>
|
||||
<StatsCard
|
||||
title="Good Signboards"
|
||||
subtitle="Informational markers"
|
||||
value={stats.totalGoodSignboard}
|
||||
icon={CheckCircle2}
|
||||
/>
|
||||
</StaggerItem> */}
|
||||
</StaggerContainer>
|
||||
|
||||
{/* Charts Row - Side by Side */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||
<Reveal delay={0.2} direction="left" className="flex flex-col">
|
||||
<Card className="flex-1 h-full">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle>Detection Distribution</CardTitle>
|
||||
<CardDescription>
|
||||
Breakdown of all detected road conditions
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex-1 pb-0">
|
||||
<DetectionDonutChart
|
||||
defectedSignboard={stats.totalDefectedSignboard}
|
||||
pothole={stats.totalPothole}
|
||||
roadCrack={stats.totalRoadCrack}
|
||||
damagedRoadMarking={stats.totalDamagedRoadMarking}
|
||||
// goodSignboard={stats.totalGoodSignboard}
|
||||
drainIssue={stats.totalDrainIssue}
|
||||
defectiveCulvert={stats.totalDefectiveCulvert}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Total detections:{' '}
|
||||
{stats.totalRoadDamage +
|
||||
// stats.totalGoodSignboard +
|
||||
stats.totalDrainIssue +
|
||||
stats.totalDefectiveCulvert}
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={0.3} direction="right" className="flex flex-col">
|
||||
<Card className="flex-1 h-full">
|
||||
<CardHeader className="items-center pb-0">
|
||||
<CardTitle className="text-base font-bold">
|
||||
Severity by Segment
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs">
|
||||
Detections grouped by road segment
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="h-[300px]">
|
||||
<ChainageBarChart
|
||||
data={stats.chainageData || []}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
</CardContent>
|
||||
<CardFooter className="flex-col gap-2 text-sm">
|
||||
<div className="leading-none text-muted-foreground">
|
||||
Analysis based on latest processed sequence
|
||||
</div>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Reveal>
|
||||
</div>
|
||||
|
||||
{/* Map - Full Width Below Charts */}
|
||||
<Reveal delay={0.4} direction="up">
|
||||
<div>
|
||||
<DashboardMap
|
||||
selectedProjectId={selectedProjectId}
|
||||
selectedPackageId={selectedPackageId}
|
||||
selectedChainageId={selectedChainageId} // Changed 'selectedLocationId' to 'selectedChainageId'
|
||||
projectSummary={projectSummary}
|
||||
/>
|
||||
</div>
|
||||
</Reveal>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,18 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { ROUTES } from '@/utils/routes';
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
import { AppLoader } from '@/components/AppLoader';
|
||||
import { useAppStore } from '@/store/app.store';
|
||||
|
||||
export default function HomePage() {
|
||||
redirect(ROUTES.DASHBOARD);
|
||||
const router = useRouter();
|
||||
const defaultRoute = useAppStore((state) => state.defaultRoute);
|
||||
|
||||
useEffect(() => {
|
||||
router.replace(defaultRoute ?? '/access');
|
||||
}, [defaultRoute, router]);
|
||||
|
||||
return <AppLoader label="Opening workspace" />;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { API_ROUTES } from '@/constants/apiRoutes';
|
||||
@@ -125,6 +125,7 @@ function mergeTicketListItem(
|
||||
|
||||
export function useTenantTicketTableEvents(enabled = true) {
|
||||
const queryClient = useQueryClient();
|
||||
const hasInvalidatedAfterConnectionErrorRef = useRef(false);
|
||||
|
||||
const events = useMemo(
|
||||
() => ({
|
||||
@@ -154,14 +155,22 @@ export function useTenantTicketTableEvents(enabled = true) {
|
||||
);
|
||||
|
||||
const onConnectionError = useCallback(() => {
|
||||
if (hasInvalidatedAfterConnectionErrorRef.current) return;
|
||||
|
||||
hasInvalidatedAfterConnectionErrorRef.current = true;
|
||||
queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
|
||||
}, [queryClient]);
|
||||
|
||||
const onConnectionOpen = useCallback(() => {
|
||||
hasInvalidatedAfterConnectionErrorRef.current = false;
|
||||
}, []);
|
||||
|
||||
useSseWithToken({
|
||||
enabled,
|
||||
getToken: ticketService.createTenantTicketEventsToken,
|
||||
getPath,
|
||||
events,
|
||||
onConnectionError,
|
||||
onConnectionOpen,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user