From ec77696157fccfc0296060f45965da11d65c7729 Mon Sep 17 00:00:00 2001 From: "santasri.pachhal" Date: Tue, 30 Jun 2026 13:01:08 +0530 Subject: [PATCH] refactor: simplify ticket lifecycle flow, remove processing and completed state --- .../components/TicketDefectClassTabs.tsx | 2 +- .../components/TicketDetailHeader.tsx | 13 +--- .../components/TicketHistoryCard.tsx | 9 +-- .../components/TicketOverviewCard.tsx | 15 +--- .../components/TicketStatusActions.tsx | 32 ++------ .../components/actions/NoTicketAction.tsx | 34 ++------- .../actions/ProcessingTicketAction.tsx | 58 --------------- src/app/(modules)/ticket/[ticketId]/page.tsx | 8 +- .../ticket/components/TicketColumns.tsx | 7 -- .../ticket/components/TicketFilters.tsx | 45 +---------- .../ticket/components/TicketStatusBadge.tsx | 1 - .../ticket/components/TicketTable.tsx | 2 +- .../ticket/hooks/useTicketDetailEvents.ts | 74 ++----------------- .../ticket/hooks/useTicketQueries.ts | 26 +------ src/app/(modules)/ticket/page.tsx | 13 +--- src/constants/apiRoutes.ts | 1 - src/services/api/ticket.service.ts | 7 -- src/types/ticket/detail.ts | 1 - src/types/ticket/events.ts | 13 ---- src/types/ticket/status.ts | 1 - 20 files changed, 36 insertions(+), 326 deletions(-) delete mode 100644 src/app/(modules)/ticket/[ticketId]/components/actions/ProcessingTicketAction.tsx diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx index ee788f9..95e984c 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketDefectClassTabs.tsx @@ -88,7 +88,7 @@ export function TicketDefectClassTabs({ Issues - + {defectClasses.length} diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx index c3349e7..f83a066 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketDetailHeader.tsx @@ -9,26 +9,19 @@ import { PERMISSIONS } from '@/constants/permissions'; import { PermissionGuard } from '@/guards'; import type { TicketDetail } from '@/types'; -import { TicketStatusBadge } from '../../components/TicketStatusBadge'; - export function TicketDetailHeader({ ticket }: { ticket: TicketDetail }) { const router = useRouter(); - const ticketLabel = ticket.ticket_name + const ticketLabel = ticket.ticket_name; return (
-
-

- Ticket Detail -

- -
+

Ticket Detail

Ticket: {ticketLabel}

- {ticket.video_id && ticket.status !== 'processing' ? ( + {ticket.video_id ? ( = { - processing: 'Incident Ticket Created', unassigned: 'Awaiting Assignment', assigned: 'Dispatched to Maintenance', under_review: 'Maintenance Finalized', closed: 'Ticket Closed', }; - return titles[status] ?? formatTicketStatus(status); + return titles[status]; } function HistoryEntry({ @@ -39,7 +36,7 @@ function HistoryEntry({ {!isLast ? ( ) : null} @@ -56,7 +53,7 @@ function HistoryEntry({ className={cn( 'size-2 rounded-full bg-primary', isClosed && - 'h-1.5 w-2.5 rotate-[-45deg] rounded-none border-b-2 border-l-2 border-current bg-transparent', + 'h-1.5 w-2.5 -rotate-45 rounded-none border-b-2 border-l-2 border-current bg-transparent', )} /> diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx index 3735edd..25f87fe 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketOverviewCard.tsx @@ -1,14 +1,7 @@ 'use client'; -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from '@/components/ui/card'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import type { TicketActor, TicketDetail } from '@/types'; -import { formatDate } from '@/utils/date'; function formatCreatedDate(value: string | null | undefined) { if (!value) return '-'; @@ -111,12 +104,6 @@ export function TicketOverviewCard({ ticket }: { ticket: TicketDetail }) { - - {ticket.ai_result?.completed_at ? ( - - AI analysis completed {formatDate(ticket.ai_result.completed_at)} - - ) : null} ); } diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx index 49eaf83..9395174 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx @@ -3,39 +3,24 @@ import { PERMISSIONS } from '@/constants/permissions'; import { PermissionGuard } from '@/guards'; import type { TicketDetail } from '@/types'; -import type { TicketDetailLiveState } from '../../hooks/useTicketDetailEvents'; import { AssignTicketAction } from './actions/AssignTicketAction'; import { ClosedTicketAction } from './actions/ClosedTicketAction'; import { NoTicketAction } from './actions/NoTicketAction'; -import { ProcessingTicketAction } from './actions/ProcessingTicketAction'; import { ReviewRepairAction } from './actions/ReviewRepairAction'; import { SubmitRepairAction } from './actions/SubmitRepairAction'; import { TicketDefectClassTabs } from './TicketDefectClassTabs'; interface TicketStatusActionsProps { ticket: TicketDetail; - liveState: TicketDetailLiveState; } -function getTicketAction( - ticket: TicketDetail, - liveState: TicketDetailLiveState, -) { - if (ticket.status === 'processing') { - return ( - - ); - } - +function getTicketAction(ticket: TicketDetail) { if (ticket.assignment_status === 'unassigned') { return ( } + fallback={} > @@ -46,7 +31,7 @@ function getTicketAction( return ( } + fallback={} > @@ -57,7 +42,7 @@ function getTicketAction( return ( } + fallback={} > @@ -68,17 +53,14 @@ function getTicketAction( return ; } - return ; + return ; } -export function TicketStatusActions({ - ticket, - liveState, -}: TicketStatusActionsProps) { +export function TicketStatusActions({ ticket }: TicketStatusActionsProps) { return (
- {getTicketAction(ticket, liveState)} + {getTicketAction(ticket)}
); } diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/NoTicketAction.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/NoTicketAction.tsx index 8e883d0..4e9c31d 100644 --- a/src/app/(modules)/ticket/[ticketId]/components/actions/NoTicketAction.tsx +++ b/src/app/(modules)/ticket/[ticketId]/components/actions/NoTicketAction.tsx @@ -1,37 +1,15 @@ 'use client'; -import { CircleOff, Clock } from 'lucide-react'; - -import type { TicketStatus } from '@/types'; +import { CircleOff } from 'lucide-react'; import { TicketActionCard } from './TicketActionCard'; -const statusConfig: Record< - 'processing', - { icon: typeof Clock; title: string; message: string } -> = { - processing: { - icon: Clock, - title: 'Processing', - message: 'This ticket is still being processed. Check back shortly.', - }, -}; - -export function NoTicketAction({ status }: { status: TicketStatus }) { - const config = - status === 'processing' - ? statusConfig[status] - : { - icon: CircleOff, - title: 'No Actions', - message: 'No action available for this ticket.', - }; - - const Icon = config.icon; - +export function NoTicketAction() { return ( - -

{config.message}

+ +

+ No action available for this ticket. +

); } diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/ProcessingTicketAction.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/ProcessingTicketAction.tsx deleted file mode 100644 index e478bc9..0000000 --- a/src/app/(modules)/ticket/[ticketId]/components/actions/ProcessingTicketAction.tsx +++ /dev/null @@ -1,58 +0,0 @@ -'use client'; - -import { AlertCircle, Loader } from 'lucide-react'; - -import { Progress } from '@/components/ui/progress'; -import type { TicketProcessingProgress } from '../../../hooks/useTicketDetailEvents'; - -import { TicketActionCard } from './TicketActionCard'; - -interface ProcessingTicketActionProps { - progress: TicketProcessingProgress | null; - errorMessage: string | null; -} - -function getProgressMessage(progress: TicketProcessingProgress | null) { - if (!progress) return 'Waiting for analysis updates.'; - if (progress.progress >= 95) { - return progress.message ?? 'Rendering annotated video...'; - } - return progress.message ?? 'Processing video.'; -} - -export function ProcessingTicketAction({ - progress, - errorMessage, -}: ProcessingTicketActionProps) { - const progressValue = progress ? progress.progress : 0; - const hasProgress = Boolean(progress); - - return ( - -
-
-
- - {getProgressMessage(progress)} - - - {hasProgress ? `${Math.round(progressValue)}%` : '--'} - -
- -
- - {errorMessage && ( -
- - {errorMessage} -
- )} -
-
- ); -} diff --git a/src/app/(modules)/ticket/[ticketId]/page.tsx b/src/app/(modules)/ticket/[ticketId]/page.tsx index 7f82cc1..d469af2 100644 --- a/src/app/(modules)/ticket/[ticketId]/page.tsx +++ b/src/app/(modules)/ticket/[ticketId]/page.tsx @@ -24,11 +24,7 @@ export default function TicketDetailPage() { const ticketQuery = useTicketDetailQuery(ticketId, defectClass); const ticket = ticketQuery.data; - const liveState = useTicketDetailEvents( - ticketId, - defectClass, - Boolean(ticketId), - ); + useTicketDetailEvents(ticketId, defectClass, Boolean(ticketId)); if (ticketQuery.isLoading) { return ; @@ -62,7 +58,7 @@ export default function TicketDetailPage() {
- +
diff --git a/src/app/(modules)/ticket/components/TicketColumns.tsx b/src/app/(modules)/ticket/components/TicketColumns.tsx index 1973756..267089c 100644 --- a/src/app/(modules)/ticket/components/TicketColumns.tsx +++ b/src/app/(modules)/ticket/components/TicketColumns.tsx @@ -5,7 +5,6 @@ import type { ColumnDef } from '@tanstack/react-table'; import type { TicketListItem } from '@/types'; -import { TicketStatusBadge } from './TicketStatusBadge'; import { formatDate } from '@/utils/date'; export function useTicketColumns(): ColumnDef[] { @@ -19,12 +18,6 @@ export function useTicketColumns(): ColumnDef[] { {row.original.ticket_name} ), }, - { - accessorKey: 'status', - header: 'Status', - size: 150, - cell: ({ row }) => , - }, { accessorKey: 'chainage_name', header: 'Segment', diff --git a/src/app/(modules)/ticket/components/TicketFilters.tsx b/src/app/(modules)/ticket/components/TicketFilters.tsx index b44f5a9..0731fdd 100644 --- a/src/app/(modules)/ticket/components/TicketFilters.tsx +++ b/src/app/(modules)/ticket/components/TicketFilters.tsx @@ -1,60 +1,19 @@ 'use client'; import { SegmentSelect } from '@/components/lookups/SegmentSelect'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; -import type { TicketStatus } from '@/types'; - -import { formatTicketStatus } from './TicketStatusBadge'; - -const ticketStatuses: TicketStatus[] = [ - 'processing', - 'unassigned', - 'assigned', - 'under_review', - 'closed', -]; interface TicketFiltersProps { - status: TicketStatus | 'all'; segmentId: string; - onStatusChange: (status: TicketStatus | 'all') => void; onSegmentChange: (segmentId: string) => void; } export function TicketFilters({ - status, segmentId, - onStatusChange, onSegmentChange, }: TicketFiltersProps) { return ( -
- - -
- -
+
+
); } diff --git a/src/app/(modules)/ticket/components/TicketStatusBadge.tsx b/src/app/(modules)/ticket/components/TicketStatusBadge.tsx index f5126fa..f5b0169 100644 --- a/src/app/(modules)/ticket/components/TicketStatusBadge.tsx +++ b/src/app/(modules)/ticket/components/TicketStatusBadge.tsx @@ -5,7 +5,6 @@ import { cn } from '@/lib/utils'; import type { TicketStatus } from '@/types'; const statusClasses: Record = { - processing: 'bg-sky-100 text-sky-700 border-sky-200', unassigned: 'bg-amber-100 text-amber-800 border-amber-200', assigned: 'bg-indigo-100 text-indigo-700 border-indigo-200', under_review: 'bg-violet-100 text-violet-700 border-violet-200', diff --git a/src/app/(modules)/ticket/components/TicketTable.tsx b/src/app/(modules)/ticket/components/TicketTable.tsx index c06e9c5..6ad9224 100644 --- a/src/app/(modules)/ticket/components/TicketTable.tsx +++ b/src/app/(modules)/ticket/components/TicketTable.tsx @@ -39,7 +39,7 @@ export function TicketTable({ isLoading={isLoading} toolbar={toolbar} emptyTitle="No tickets found." - emptyDescription="Ticket rows will appear after video processing creates them." + emptyDescription="Tickets will appear when road issues are detected." pagination={{ skip, limit, diff --git a/src/app/(modules)/ticket/hooks/useTicketDetailEvents.ts b/src/app/(modules)/ticket/hooks/useTicketDetailEvents.ts index b727733..b98c0a4 100644 --- a/src/app/(modules)/ticket/hooks/useTicketDetailEvents.ts +++ b/src/app/(modules)/ticket/hooks/useTicketDetailEvents.ts @@ -1,30 +1,15 @@ 'use client'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useQueryClient } from '@tanstack/react-query'; -import { useRouter } from 'next/navigation'; import { API_ROUTES } from '@/constants/apiRoutes'; import { useSseWithToken } from '@/hooks/sse/useSseWithToken'; import { ticketService } from '@/services/api'; -import type { - TicketDetailStatusEvent, - TicketProgressEvent, - TicketStreamErrorEvent, -} from '@/types'; +import type { TicketDetailStatusEvent } from '@/types'; import { ticketKeys } from '../queries/ticketKeys'; -export interface TicketProcessingProgress { - progress: number; - message: string | null; -} - -export interface TicketDetailLiveState { - progress: TicketProcessingProgress | null; - errorMessage: string | null; -} - const TICKET_DETAIL_RECONNECT_EVENTS = ['error'] as const; function isHeartbeatEvent(event: { type?: string } | null | undefined) { @@ -35,14 +20,9 @@ export function useTicketDetailEvents( ticketId: string | undefined, defectClass: string | undefined, enabled = true, -): TicketDetailLiveState { - const router = useRouter(); +): void { const queryClient = useQueryClient(); const defectClassRef = useRef(defectClass); - const [progress, setProgress] = useState( - null, - ); - const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { defectClassRef.current = defectClass; @@ -60,18 +40,6 @@ export function useTicketDetailEvents( queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); }, [queryClient, ticketId]); - const selectDefectClass = useCallback( - (nextDefectClass: string | null | undefined) => { - if (!ticketId || !nextDefectClass || defectClassRef.current) return; - - router.replace( - `/ticket/${ticketId}?defect_class=${encodeURIComponent(nextDefectClass)}`, - { scroll: false }, - ); - }, - [router, ticketId], - ); - const getToken = useCallback( () => ticketService.createTicketEventsToken(ticketId as string), [ticketId], @@ -89,30 +57,10 @@ export function useTicketDetailEvents( if (isHeartbeatEvent(event)) return; refetchTicket(); }, - progress: (event: TicketProgressEvent) => { - setErrorMessage(null); - setProgress({ - progress: event.progress, - message: event.message, - }); - }, - complete: (event: TicketProgressEvent) => { - setErrorMessage(null); - setProgress({ - progress: 100, - message: event.message, - }); - selectDefectClass(event.defect_class); - refetchTicket(); - }, - error: (event: TicketStreamErrorEvent) => { - setErrorMessage( - event.message ?? 'Live ticket updates disconnected. Reconnecting...', - ); - }, + error: () => undefined, heartbeat: () => undefined, }), - [refetchTicket, selectDefectClass], + [refetchTicket], ); const shouldStop = useCallback( @@ -122,15 +70,6 @@ export function useTicketDetailEvents( [], ); - const onConnectionError = useCallback(() => { - setErrorMessage('Live ticket updates disconnected. Reconnecting...'); - }, []); - - useEffect(() => { - setProgress(null); - setErrorMessage(null); - }, [ticketId]); - useSseWithToken({ enabled: Boolean(enabled && ticketId), getToken, @@ -138,8 +77,5 @@ export function useTicketDetailEvents( events, reconnectOnEvents: TICKET_DETAIL_RECONNECT_EVENTS, shouldStop, - onConnectionError, }); - - return { progress, errorMessage }; } diff --git a/src/app/(modules)/ticket/hooks/useTicketQueries.ts b/src/app/(modules)/ticket/hooks/useTicketQueries.ts index c6336b0..1dff49f 100644 --- a/src/app/(modules)/ticket/hooks/useTicketQueries.ts +++ b/src/app/(modules)/ticket/hooks/useTicketQueries.ts @@ -23,29 +23,26 @@ const TICKET_TABLE_REFRESH_MS = 5 * 60 * 1000; interface UseTicketsQueryParams { skip: number; limit: number; - status?: TicketStatus | 'all'; chainageId?: string; } function buildTicketListParams({ skip, limit, - status, chainageId, }: UseTicketsQueryParams): TicketListParams { return { skip, limit, - status: status === 'all' ? undefined : status, chainage_id: chainageId || undefined, }; } export function useTicketsQuery(params: UseTicketsQueryParams) { - const { skip, limit, status, chainageId } = params; + const { skip, limit, chainageId } = params; const listParams = useMemo( - () => buildTicketListParams({ skip, limit, status, chainageId }), - [chainageId, limit, skip, status], + () => buildTicketListParams({ skip, limit, chainageId }), + [chainageId, limit, skip], ); return useQuery({ @@ -115,23 +112,6 @@ export function useAssignTicketMutation(ticketId: string) { }); } -export function useStartTicketMutation(ticketId: string) { - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: () => ticketService.startTicket(ticketId), - onSuccess: (ticket) => { - toast.success('Ticket started'); - queryClient.setQueryData( - ticketKeys.detail(ticketId), - (current) => mergeTicketDetailResponse(current, ticket), - ); - queryClient.invalidateQueries({ queryKey: ticketKeys.lists() }); - }, - onError: () => toast.error('Failed to start ticket'), - }); -} - export function useSubmitRepairMutation(ticketId: string) { const queryClient = useQueryClient(); diff --git a/src/app/(modules)/ticket/page.tsx b/src/app/(modules)/ticket/page.tsx index 7df18bd..e670019 100644 --- a/src/app/(modules)/ticket/page.tsx +++ b/src/app/(modules)/ticket/page.tsx @@ -4,7 +4,7 @@ import { useCallback, useMemo, useState } from 'react'; import { useRouter } from 'next/navigation'; import { PageHeader } from '@/components/page-header'; import { Ticket } from 'lucide-react'; -import type { TicketListItem, TicketStatus } from '@/types'; +import type { TicketListItem } from '@/types'; import { TicketFilters } from './components/TicketFilters'; import { TicketTable } from './components/TicketTable'; @@ -16,7 +16,6 @@ export default function TicketPage() { const router = useRouter(); const [skip, setSkip] = useState(0); const [limit, setLimit] = useState(10); - const [status, setStatus] = useState('all'); const [segmentId, setSegmentId] = useState(''); useTenantTicketTableEvents(); @@ -24,7 +23,6 @@ export default function TicketPage() { const ticketsQuery = useTicketsQuery({ skip, limit, - status, chainageId: segmentId, }); @@ -32,11 +30,6 @@ export default function TicketPage() { const tickets = ticketsQuery.data?.items ?? []; const total = ticketsQuery.data?.total ?? 0; - const handleStatusChange = useCallback((nextStatus: TicketStatus | 'all') => { - setStatus(nextStatus); - setSkip(0); - }, []); - const handleSegmentChange = useCallback((nextSegmentId: string) => { setSegmentId(nextSegmentId); setSkip(0); @@ -56,13 +49,11 @@ export default function TicketPage() { const toolbar = useMemo( () => ( ), - [handleSegmentChange, handleStatusChange, segmentId, status], + [handleSegmentChange, segmentId], ); return ( diff --git a/src/constants/apiRoutes.ts b/src/constants/apiRoutes.ts index 0eac521..700d23f 100644 --- a/src/constants/apiRoutes.ts +++ b/src/constants/apiRoutes.ts @@ -59,7 +59,6 @@ export const API_ROUTES = { `/biz/api/v1/tickets/${id}/class-detail?defect_class=${defectClass}`, DEFECT_CLASSES: (id: string) => `/biz/api/v1/tickets/${id}/defect-classes`, ASSIGN: (id: string) => `/biz/api/v1/tickets/${id}/assign`, - START: (id: string) => `/biz/api/v1/tickets/${id}/start`, SUBMIT_REPAIR: (id: string) => `/biz/api/v1/tickets/${id}/submit-repair`, SUBMIT_REPAIR_UPLOAD: (id: string) => `/biz/api/v1/tickets/${id}/repair-proof`, diff --git a/src/services/api/ticket.service.ts b/src/services/api/ticket.service.ts index fc8cbab..e639c76 100644 --- a/src/services/api/ticket.service.ts +++ b/src/services/api/ticket.service.ts @@ -81,13 +81,6 @@ export const ticketService = { return response.data; }, - startTicket: async (ticketId: string): Promise => { - const response = await axiosClient.post( - API_ROUTES.TICKETS.START(ticketId), - ); - return response.data; - }, - submitRepair: async ( ticketId: string, payload: SubmitRepairPayload, diff --git a/src/types/ticket/detail.ts b/src/types/ticket/detail.ts index 60186c4..c0d4394 100644 --- a/src/types/ticket/detail.ts +++ b/src/types/ticket/detail.ts @@ -52,7 +52,6 @@ export interface TicketReviewer extends TicketActor { export interface TicketAiResult { detection_count: number; - completed_at: string | null; available_defect_classes: string[]; } diff --git a/src/types/ticket/events.ts b/src/types/ticket/events.ts index 766f33d..0d9879d 100644 --- a/src/types/ticket/events.ts +++ b/src/types/ticket/events.ts @@ -30,16 +30,3 @@ export type TicketDetailStatusEvent = Partial & { status?: TicketStatus; message?: string; }; - -export interface TicketProgressEvent { - type: 'progress' | 'complete'; - status: string; - progress: number; - message: string; - defect_class?: string | null; -} - -export interface TicketStreamErrorEvent { - type?: 'error'; - message?: string; -} diff --git a/src/types/ticket/status.ts b/src/types/ticket/status.ts index 0048567..92c94c4 100644 --- a/src/types/ticket/status.ts +++ b/src/types/ticket/status.ts @@ -1,5 +1,4 @@ export type TicketStatus = - | 'processing' | 'unassigned' | 'assigned' | 'under_review'