From d88978a830e6e93e6d33cfbf7624982f5c41cdb4 Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Tue, 7 Jul 2026 12:02:29 +0530 Subject: [PATCH] chore: class location change --- .../components/TicketDefectClassTabs.tsx | 187 +++++++++++++----- .../components/TicketDetailHeader.tsx | 2 +- .../components/TicketOverviewCard.tsx | 180 +++++++---------- src/app/(modules)/ticket/[ticketId]/page.tsx | 4 +- src/types/ticket/detail.ts | 21 +- 5 files changed, 229 insertions(+), 165 deletions(-) diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx index a0a9745..52b98da 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx @@ -2,22 +2,27 @@ import { useEffect, useMemo, useState } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { AlertTriangle, Component } from 'lucide-react'; +import { CalendarCheck2, Component, FileVideo, ListChecks } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; -import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Card, CardContent } from '@/components/ui/card'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { DEFECT_CLASS_STATUS_CONFIG } from '@/constants/defectClassStatus'; +import { getDefectVisual } from '@/constants/defectVisualConfig'; import { cn } from '@/lib/utils'; +import type { TicketOverviewDetail } from '@/types'; +import { formatDate } from '@/utils/date'; import { useTicketDefectClassesQuery } from '../../hooks/useTicketQueries'; interface TicketDefectClassTabsProps { ticketId: string; + ticket?: TicketOverviewDetail; } export function TicketDefectClassTabs({ ticketId, + ticket, }: TicketDefectClassTabsProps) { const router = useRouter(); const pathname = usePathname(); @@ -56,20 +61,27 @@ export function TicketDefectClassTabs({ setSelectedClass(className); router.replace(`${pathname}?${nextParams.toString()}`, { scroll: false }); }; + const selectedIssue = ticket?.ai_result.detections_by_class.find( + (item) => item.class_name === selectedClass, + ); + const selectedIssueVisual = getDefectVisual(selectedIssue?.class_name ?? ''); + const SelectedIssueIcon = selectedIssueVisual.icon; + const completedAt = ticket?.ai_result.completed_at; + const videoName = ticket?.video?.name; if (defectClassesQuery.isLoading) { return ( - +
- - +
{Array.from({ length: 3 }).map((_, index) => (
))} +
); @@ -87,49 +99,136 @@ export function TicketDefectClassTabs({ className="w-full" > - - - - Issues - - {defectClasses.length} - - - - - - {defectClasses.map((item) => { - const statusConfig = - DEFECT_CLASS_STATUS_CONFIG[item.assignment_status]; + +
+
+ +

Detected Issues

+ + {defectClasses.length} + +
- return ( - - - {item.display_name} - - + {defectClasses.map((item) => { + const statusConfig = + DEFECT_CLASS_STATUS_CONFIG[item.assignment_status]; + + return ( + - - {statusConfig.label} - - - ); - })} - + + {item.display_name} + + + + {statusConfig.label} + + + ); + })} + +
+ +
+ {selectedIssue ? ( +
+
+ + + +
+

+ {selectedIssue.display_name} +

+ + {selectedIssue.count} Detections + +
+
+ +
+
+
+

+ Current Issue +

+

+ {selectedIssue.display_name} +

+
+
+

+ Current Issue Count +

+

+ {selectedIssue.count} +

+
+ {completedAt ? ( +
+
+ + AI Completed At +
+

+ {formatDate(completedAt)} +

+
+ ) : null} + {videoName ? ( +
+
+ + Video +
+

+ {videoName} +

+
+ ) : null} +
+
+
+ ) : ( +
+

+ Select an issue to view details. +

+
+ )} +
diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx index 082c1cc..b3a40b3 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx @@ -12,7 +12,7 @@ export function TicketDetailHeader({ ticket: TicketOverviewDetail; }) { const router = useRouter(); - const ticketLabel = ticket.ticket_name; + const ticketLabel = ticket.ticket_name || ticket.id; return (
diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx index 5ad1115..aed7a9d 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx @@ -6,7 +6,6 @@ import { CalendarDays, Clock3, Gauge, - LayoutDashboard, MapPin, Monitor, UserRound, @@ -62,13 +61,11 @@ function SummaryCard({ value, colorClassName, cardClassName, - icon: Icon, }: { label: string; value: number; colorClassName: string; cardClassName: string; - icon?: IconComponent; }) { return (
-
- {label} -

- {value} -

-
- -
- ); -} - export function TicketOverviewCard({ ticket, - defectClass, }: { ticket: TicketOverviewDetail; - defectClass?: string; }) { const router = useRouter(); - const currentIssue = - ticket.ai_result.detections_by_class.find( - (item) => item.class_name === defectClass, - ) ?? ticket.ai_result.detections_by_class[0]; - const currentVisual = getDefectVisual(currentIssue?.class_name ?? ''); - const minutes = Math.floor(ticket.video_metadata.duration_seconds / 60); - const seconds = Math.floor(ticket.video_metadata.duration_seconds % 60); - const duration = `${minutes}:${seconds.toString().padStart(2, '0')}`; + const videoMetadata = ticket.video_metadata; + const duration = + typeof videoMetadata?.duration_seconds === 'number' + ? `${Math.floor(videoMetadata.duration_seconds / 60)}:${Math.floor( + videoMetadata.duration_seconds % 60, + ) + .toString() + .padStart(2, '0')}` + : null; const chainageName = ticket.location?.chainage?.name || ticket.chainage_name || null; const packageName = ticket.location?.package?.name || null; const locationLabel = - [chainageName, packageName].filter(Boolean).join(', ') || '—'; + [chainageName, packageName].filter(Boolean).join(', ') || null; + const uploadedOn = + ticket.timestamps?.created_at || ticket.ai_result.completed_at || null; + const uploaderName = ticket.uploader?.name || null; + const analysisVideoId = ticket.video_id || ticket.video?.id || null; + const videoName = ticket.video?.name || null; + const hasVideoMetrics = Boolean( + videoName || + videoMetadata?.fps || + duration || + videoMetadata?.resolution?.label, + ); return (
- - -
- - - {formatDate(ticket.timestamps.created_at)} - -
-
- - - {ticket.uploader.name} - -
-
- - - {locationLabel} - -
-
-
+ {uploadedOn || uploaderName || locationLabel ? ( + + + {uploadedOn ? ( +
+ + + {formatDate(uploadedOn)} + +
+ ) : null} + {uploaderName ? ( +
+ + + {uploaderName} + +
+ ) : null} + {locationLabel ? ( +
+ + + {locationLabel} + +
+ ) : null} +
+
+ ) : null} @@ -185,14 +171,14 @@ export function TicketOverviewCard({ AI Detection Summary - {ticket.video_id ? ( + {analysisVideoId ? (
-
- - - -
- - - - - - - - Ticket Overview - - - - - - + {hasVideoMetrics ? ( +
+ {videoName ? ( + + ) : null} + {videoMetadata?.fps ? ( + + ) : null} + {duration ? ( + + ) : null} + {videoMetadata?.resolution?.label ? ( + + ) : null} +
+ ) : null}
diff --git a/src/app/(modules)/ticket/[ticketId]/page.tsx b/src/app/(modules)/ticket/[ticketId]/page.tsx index 6b3aeef..156129b 100644 --- a/src/app/(modules)/ticket/[ticketId]/page.tsx +++ b/src/app/(modules)/ticket/[ticketId]/page.tsx @@ -68,10 +68,11 @@ export default function TicketDetailPage() {
{overview ? ( - + ) : ( )} + {isClassDetailLoading ? : null} {!isClassDetailLoading && classDetail ? ( @@ -79,7 +80,6 @@ export default function TicketDetailPage() {
- {isClassDetailLoading ? : null} {!isClassDetailLoading && classDetail ? ( diff --git a/src/types/ticket/detail.ts b/src/types/ticket/detail.ts index 491aa59..9595d6e 100644 --- a/src/types/ticket/detail.ts +++ b/src/types/ticket/detail.ts @@ -116,24 +116,25 @@ export interface TicketDetectionClassCount { export interface TicketOverviewDetail { id: string; - ticket_name: string; - video_id: string; - chainage_id: string; - chainage_name: string; + ticket_name?: string | null; + video_id?: string | null; + video?: TicketVideo | null; + chainage_id?: string | null; + chainage_name?: string | null; location: TicketLocation | null; - video_metadata: TicketVideoMetadata; - uploader: { + video_metadata?: TicketVideoMetadata | null; + uploader?: { user_id: number; - name: string; - email: string; - }; + name: string | null; + email: string | null; + } | null; ai_result: { detection_count: number; completed_at: string; available_defect_classes: string[]; detections_by_class: TicketDetectionClassCount[]; }; - timestamps: TicketTimestamps; + timestamps?: TicketTimestamps | null; } export interface TicketDetail {