From e077467bce23254e43d9251e9e059d7f0e319bd0 Mon Sep 17 00:00:00 2001
From: "santasri.pachhal"
Date: Wed, 22 Jul 2026 15:41:38 +0530
Subject: [PATCH] refactor(ticket): remove web repair upload flow
---
.../review/components/IssueEvidencePanel.tsx | 9 +-
.../components/TicketStatusActions.tsx | 4 +-
.../components/actions/RepairEvidenceForm.tsx | 485 ------------------
.../actions/RequestExtensionAction.tsx | 14 +
.../actions/RequestExtensionForm.tsx | 30 +-
.../components/actions/SubmitRepairAction.tsx | 52 --
.../actions/TicketExtensionRequestPanel.tsx | 12 +-
.../ticket/hooks/useTicketQueries.ts | 38 --
src/constants/apiRoutes.ts | 3 -
src/services/api/ticket.service.ts | 37 --
src/types/ticket/actions.ts | 12 -
11 files changed, 35 insertions(+), 661 deletions(-)
delete mode 100644 src/app/(modules)/ticket/[ticketId]/components/actions/RepairEvidenceForm.tsx
create mode 100644 src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionAction.tsx
delete mode 100644 src/app/(modules)/ticket/[ticketId]/components/actions/SubmitRepairAction.tsx
diff --git a/src/app/(modules)/ticket/[ticketId]/[defectClass]/review/components/IssueEvidencePanel.tsx b/src/app/(modules)/ticket/[ticketId]/[defectClass]/review/components/IssueEvidencePanel.tsx
index 4be1d3f..ef54280 100644
--- a/src/app/(modules)/ticket/[ticketId]/[defectClass]/review/components/IssueEvidencePanel.tsx
+++ b/src/app/(modules)/ticket/[ticketId]/[defectClass]/review/components/IssueEvidencePanel.tsx
@@ -1,4 +1,4 @@
-import { CalendarCheck2, CheckCircle2, ImageIcon, MapPin } from 'lucide-react';
+import { CalendarCheck2, ImageIcon, MapPin, Smartphone } from 'lucide-react';
import DetectionLocationMap from '@/components/map/detectionLocationMap';
import { Badge } from '@/components/ui/badge';
@@ -130,8 +130,8 @@ export function IssueEvidencePanel({
{repairProof?.submitted ? (
-
- Repair Submitted
+
+ Uploaded from App
) : null}
@@ -151,7 +151,8 @@ export function IssueEvidencePanel({
{repairProof.submitted_at ? (
- Submitted {formatDate(repairProof.submitted_at)}
+ Uploaded from the field app on{' '}
+ {formatDate(repairProof.submitted_at)}
) : null}
diff --git a/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx b/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx
index f57846a..1b02c7f 100644
--- a/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx
+++ b/src/app/(modules)/ticket/[ticketId]/components/TicketStatusActions.tsx
@@ -9,8 +9,8 @@ import { AssignTicketAction } from './actions/AssignTicketAction';
import { ClosedTicketAction } from './actions/ClosedTicketAction';
import { NoTicketAction } from './actions/NoTicketAction';
import { OpenRepairReviewAction } from './actions/OpenRepairReviewAction';
+import { RequestExtensionAction } from './actions/RequestExtensionAction';
import { ReviewExtensionRequestAction } from './actions/ReviewExtensionRequestAction';
-import { SubmitRepairAction } from './actions/SubmitRepairAction';
interface TicketStatusActionsProps {
ticket: TicketDetail;
@@ -37,7 +37,7 @@ function getTicketAction(ticket: TicketDetail) {
permissions={PERMISSIONS.TICKET.WORK}
fallback={}
>
-
+
}
>
diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/RepairEvidenceForm.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/RepairEvidenceForm.tsx
deleted file mode 100644
index f321147..0000000
--- a/src/app/(modules)/ticket/[ticketId]/components/actions/RepairEvidenceForm.tsx
+++ /dev/null
@@ -1,485 +0,0 @@
-'use client';
-
-import { useEffect, useRef, useState, type ReactNode } from 'react';
-import {
- Camera,
- ImageIcon,
- Loader2,
- Play,
- Plus,
- Send,
- Video,
- X,
-} from 'lucide-react';
-
-import { Button } from '@/components/ui/button';
-import { Label } from '@/components/ui/label';
-import { Textarea } from '@/components/ui/textarea';
-import { cn } from '@/lib/utils';
-
-import { useSubmitRepairUploadMutation } from '../../../hooks/useTicketQueries';
-
-const MAX_IMAGES = 30;
-const MAX_NOTES = 500;
-
-function RequiredMark() {
- return *;
-}
-
-function formatVideoDuration(seconds: number) {
- const totalSeconds = Math.floor(seconds);
- const minutes = Math.floor(totalSeconds / 60);
- const remainingSeconds = totalSeconds % 60;
- return `${String(minutes).padStart(2, '0')}:${String(remainingSeconds).padStart(2, '0')}`;
-}
-
-interface MediaSectionHeaderProps {
- icon: ReactNode;
- title: string;
- addLabel: string;
- onAdd: () => void;
- disabled?: boolean;
-}
-
-function MediaSectionHeader({
- icon,
- title,
- addLabel,
- onAdd,
- disabled = false,
-}: MediaSectionHeaderProps) {
- return (
-
-
- {icon}
- {title}
-
-
-
- );
-}
-
-function MediaHint({ children }: { children: ReactNode }) {
- return (
-
- {children}
-
- );
-}
-
-interface EmptyMediaPlaceholderProps {
- icon: ReactNode;
- title: string;
- description: string;
- className?: string;
- disabled?: boolean;
- onClick: () => void;
-}
-
-function EmptyMediaPlaceholder({
- icon,
- title,
- description,
- className,
- disabled = false,
- onClick,
-}: EmptyMediaPlaceholderProps) {
- return (
-
- );
-}
-
-interface ImageThumbnailProps {
- file: File;
- previewUrl: string;
- disabled?: boolean;
- onRemove: () => void;
-}
-
-function ImageThumbnail({
- file,
- previewUrl,
- disabled = false,
- onRemove,
-}: ImageThumbnailProps) {
- return (
-
- {/* eslint-disable-next-line @next/next/no-img-element */}
-

-
-
- );
-}
-
-interface VideoThumbnailProps {
- file: File;
- previewUrl: string;
- duration: string | null;
- disabled?: boolean;
- onRemove: () => void;
-}
-
-function VideoThumbnail({
- file,
- previewUrl,
- duration,
- disabled = false,
- onRemove,
-}: VideoThumbnailProps) {
- return (
-
-
-
- {duration ? (
-
- {duration}
-
- ) : null}
-
-
- );
-}
-
-interface RepairEvidenceFormProps {
- ticketId: string;
- defectClass: string;
-}
-
-export function RepairEvidenceForm({
- ticketId,
- defectClass,
-}: RepairEvidenceFormProps) {
- const imageInputRef = useRef(null);
- const videoInputRef = useRef(null);
- const [notes, setNotes] = useState('');
- const [videoFile, setVideoFile] = useState(null);
- const [videoPreview, setVideoPreview] = useState(null);
- const [videoDuration, setVideoDuration] = useState(null);
- const [imageFiles, setImageFiles] = useState([]);
- const [imagePreviews, setImagePreviews] = useState([]);
- const [error, setError] = useState(null);
- const submitRepairUploadMutation = useSubmitRepairUploadMutation(ticketId);
- const isSubmitting = submitRepairUploadMutation.isPending;
- const canSubmit = Boolean(notes.trim() && videoFile);
- const canAddMoreImages = imageFiles.length < MAX_IMAGES;
-
- useEffect(() => {
- const urls = imageFiles.map((file) => URL.createObjectURL(file));
- setImagePreviews(urls);
- return () => {
- urls.forEach((url) => URL.revokeObjectURL(url));
- };
- }, [imageFiles]);
-
- useEffect(() => {
- if (!videoFile) {
- setVideoPreview(null);
- setVideoDuration(null);
- return;
- }
-
- const url = URL.createObjectURL(videoFile);
- setVideoPreview(url);
- setVideoDuration(null);
-
- let cancelled = false;
- const video = document.createElement('video');
- video.preload = 'metadata';
- video.onloadedmetadata = () => {
- if (cancelled || !Number.isFinite(video.duration)) return;
- setVideoDuration(formatVideoDuration(video.duration));
- };
- video.src = url;
-
- return () => {
- cancelled = true;
- URL.revokeObjectURL(url);
- };
- }, [videoFile]);
-
- const addImages = (files: FileList | null) => {
- if (!files?.length) return;
-
- const remaining = MAX_IMAGES - imageFiles.length;
- if (remaining <= 0) {
- setError(`Only ${MAX_IMAGES} images can be uploaded.`);
- return;
- }
-
- const nextImages = [
- ...imageFiles,
- ...Array.from(files).slice(0, remaining),
- ];
- setImageFiles(nextImages);
- setError(
- files.length > remaining
- ? `Only ${MAX_IMAGES} images can be uploaded.`
- : null,
- );
- };
-
- const selectVideo = (files: FileList | null) => {
- setVideoFile(files?.[0] ?? null);
- setError(null);
- };
-
- const removeVideo = () => {
- setVideoFile(null);
- setError(null);
- };
-
- const removeImage = (index: number) => {
- setImageFiles((current) =>
- current.filter((_, itemIndex) => itemIndex !== index),
- );
- setError(null);
- };
-
- const handleSubmit = async () => {
- if (!notes.trim() || !videoFile) {
- setError('Repair notes and a video are required.');
- return;
- }
-
- setError(null);
- try {
- await submitRepairUploadMutation.mutateAsync({
- defect_class: defectClass,
- notes: notes.trim(),
- video: videoFile,
- images: imageFiles,
- });
- setNotes('');
- setVideoFile(null);
- setImageFiles([]);
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Upload failed');
- }
- };
-
- const openImagePicker = () => {
- if (!isSubmitting && canAddMoreImages) {
- imageInputRef.current?.click();
- }
- };
-
- const openVideoPicker = () => {
- if (!isSubmitting) {
- videoInputRef.current?.click();
- }
- };
-
- return (
-
-
-
-
-
Instructions
-
- Upload clear photos and one video of the repaired area and provide
- relevant details.
-
-
-
-
-
{
- addImages(event.target.files);
- event.target.value = '';
- }}
- />
-
{
- selectVideo(event.target.files);
- event.target.value = '';
- }}
- />
-
-
- }
- title="Photos"
- addLabel="Add Photos"
- onAdd={openImagePicker}
- disabled={isSubmitting || !canAddMoreImages}
- />
-
-
- {imageFiles.length === 0 ? (
-
}
- title="No photos added yet"
- description="Tap to upload repair photos"
- disabled={isSubmitting}
- onClick={openImagePicker}
- className="min-h-26"
- />
- ) : (
-
- {imageFiles.map((file, index) => (
- removeImage(index)}
- />
- ))}
-
- )}
-
-
-
- You can upload up to {MAX_IMAGES} images (JPG, PNG)
-
-
-
-
-
-
- Video
-
-
-
- {!videoFile || !videoPreview ? (
- }
- title="No video added yet"
- description="Tap to upload a repair video"
- disabled={isSubmitting}
- onClick={openVideoPicker}
- className="min-h-[104px]"
- />
- ) : (
-
- )}
-
-
- You can upload one video (MP4, MOV, AVI)
-
-
-
-
- {error ? (
-
- {error}
-
- ) : null}
-
-
-
- );
-}
diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionAction.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionAction.tsx
new file mode 100644
index 0000000..9ffe907
--- /dev/null
+++ b/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionAction.tsx
@@ -0,0 +1,14 @@
+import { Clock3 } from 'lucide-react';
+
+import type { TicketDetail } from '@/types';
+
+import { TicketActionCard } from './TicketActionCard';
+import { TicketExtensionRequestPanel } from './TicketExtensionRequestPanel';
+
+export function RequestExtensionAction({ ticket }: { ticket: TicketDetail }) {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionForm.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionForm.tsx
index 404d414..0bc0c88 100644
--- a/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionForm.tsx
+++ b/src/app/(modules)/ticket/[ticketId]/components/actions/RequestExtensionForm.tsx
@@ -16,13 +16,9 @@ const MAX_REASON_LENGTH = 300;
interface RequestExtensionFormProps {
ticket: TicketDetail;
- onCancel: () => void;
}
-export function RequestExtensionForm({
- ticket,
- onCancel,
-}: RequestExtensionFormProps) {
+export function RequestExtensionForm({ ticket }: RequestExtensionFormProps) {
const currentDueAt = ticket.worker?.due_at;
const currentDueDate = useMemo(
() => (currentDueAt ? new Date(currentDueAt) : undefined),
@@ -66,7 +62,6 @@ export function RequestExtensionForm({
});
setRequestedDueDate(undefined);
setReason('');
- onCancel();
};
return (
@@ -138,20 +133,15 @@ export function RequestExtensionForm({
This request will be sent to your manager for approval.
-
-
-
-
+
);
}
diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/SubmitRepairAction.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/SubmitRepairAction.tsx
deleted file mode 100644
index 728faf3..0000000
--- a/src/app/(modules)/ticket/[ticketId]/components/actions/SubmitRepairAction.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import { Wrench } from 'lucide-react';
-
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import type { TicketDetail } from '@/types';
-
-import { RepairEvidenceForm } from './RepairEvidenceForm';
-import { TicketExtensionRequestPanel } from './TicketExtensionRequestPanel';
-import { TicketActionCard } from './TicketActionCard';
-
-type RepairActionTab = 'repair-proof' | 'request-extension';
-
-export function SubmitRepairAction({ ticket }: { ticket: TicketDetail }) {
- const [activeTab, setActiveTab] = useState('repair-proof');
-
- if (ticket.repair?.submitted_at) {
- return null;
- }
-
- return (
-
- setActiveTab(value as RepairActionTab)}
- className="gap-5"
- >
-
-
- Upload Repair Proof
-
-
- Request Extension
-
-
-
-
-
-
- setActiveTab('repair-proof')}
- />
-
-
-
- );
-}
diff --git a/src/app/(modules)/ticket/[ticketId]/components/actions/TicketExtensionRequestPanel.tsx b/src/app/(modules)/ticket/[ticketId]/components/actions/TicketExtensionRequestPanel.tsx
index 69524c6..6c3d392 100644
--- a/src/app/(modules)/ticket/[ticketId]/components/actions/TicketExtensionRequestPanel.tsx
+++ b/src/app/(modules)/ticket/[ticketId]/components/actions/TicketExtensionRequestPanel.tsx
@@ -23,11 +23,6 @@ import { formatDate } from '@/utils/date';
import { useTicketExtensionRequestQuery } from '../../../hooks/useTicketQueries';
import { RequestExtensionForm } from './RequestExtensionForm';
-interface TicketExtensionRequestPanelProps {
- ticket: TicketDetail;
- onCancelRequest: () => void;
-}
-
interface DetailRowProps {
label: string;
value: string;
@@ -192,8 +187,9 @@ function ExtensionRequestDetails({
export function TicketExtensionRequestPanel({
ticket,
- onCancelRequest,
-}: TicketExtensionRequestPanelProps) {
+}: {
+ ticket: TicketDetail;
+}) {
const assignmentId = ticket.worker?.assignment_id;
const extensionRequestQuery = useTicketExtensionRequestQuery(
ticket.id,
@@ -241,7 +237,7 @@ export function TicketExtensionRequestPanel({
: null;
if (!latestRequest) {
- return ;
+ return ;
}
return ;
diff --git a/src/app/(modules)/ticket/hooks/useTicketQueries.ts b/src/app/(modules)/ticket/hooks/useTicketQueries.ts
index c2acdc1..76d5e00 100644
--- a/src/app/(modules)/ticket/hooks/useTicketQueries.ts
+++ b/src/app/(modules)/ticket/hooks/useTicketQueries.ts
@@ -17,8 +17,6 @@ import type {
RequestTicketExtensionPayload,
ReviewDetectionRepairProofPayload,
ReviewTicketExtensionPayload,
- SubmitRepairPayload,
- SubmitRepairUploadPayload,
TicketDetail,
TicketListParams,
DetectionProofStatus,
@@ -296,42 +294,6 @@ export function useAssignTicketMutation(ticketId: string) {
});
}
-export function useSubmitRepairMutation(ticketId: string) {
- const queryClient = useQueryClient();
-
- return useMutation({
- mutationFn: (payload: SubmitRepairPayload) =>
- ticketService.submitRepair(ticketId, payload),
- onSuccess: (ticket) => {
- toast.success('Repair submitted');
- queryClient.setQueryData(
- ticketKeys.classDetail(ticketId, ticket.defect_class),
- (current) => mergeTicketDetailResponse(current, ticket),
- );
- queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
- },
- onError: () => toast.error('Failed to submit repair'),
- });
-}
-
-export function useSubmitRepairUploadMutation(ticketId: string) {
- const queryClient = useQueryClient();
-
- return useMutation({
- mutationFn: (payload: SubmitRepairUploadPayload) =>
- ticketService.submitRepairUpload(ticketId, payload),
- onSuccess: (ticket, payload) => {
- toast.success('Repair evidence uploaded');
- queryClient.setQueryData(
- ticketKeys.classDetail(ticketId, payload.defect_class),
- (current) => mergeTicketDetailResponse(current, ticket),
- );
- queryClient.invalidateQueries({ queryKey: ticketKeys.lists() });
- },
- onError: () => toast.error('Failed to upload repair evidence'),
- });
-}
-
export function useRequestTicketExtensionMutation(
ticketId: string,
assignmentId: number | undefined,
diff --git a/src/constants/apiRoutes.ts b/src/constants/apiRoutes.ts
index 6c525d4..715f54c 100644
--- a/src/constants/apiRoutes.ts
+++ b/src/constants/apiRoutes.ts
@@ -68,9 +68,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`,
- SUBMIT_REPAIR: (id: string) => `/biz/api/v1/tickets/${id}/submit-repair`,
- SUBMIT_REPAIR_UPLOAD: (id: string) =>
- `/biz/api/v1/tickets/${id}/repair-proof`,
REQUEST_EXTENSION: (ticketId: string, assignmentId: number) =>
`/biz/api/v1/tickets/${ticketId}/assignments/${assignmentId}/extension-requests`,
EXTENSION_REQUESTS: (ticketId: string) =>
diff --git a/src/services/api/ticket.service.ts b/src/services/api/ticket.service.ts
index 7cffc9b..27059d6 100644
--- a/src/services/api/ticket.service.ts
+++ b/src/services/api/ticket.service.ts
@@ -8,8 +8,6 @@ import type {
RequestTicketExtensionPayload,
ReviewTicketExtensionPayload,
SseTokenResponse,
- SubmitRepairPayload,
- SubmitRepairUploadPayload,
TicketDetail,
TicketExtensionRequestsResponse,
TicketOverviewDetail,
@@ -90,41 +88,6 @@ export const ticketService = {
return response.data;
},
- submitRepair: async (
- ticketId: string,
- payload: SubmitRepairPayload,
- ): Promise => {
- const response = await axiosClient.post(
- API_ROUTES.TICKETS.SUBMIT_REPAIR(ticketId),
- payload,
- );
- return response.data;
- },
-
- submitRepairUpload: async (
- ticketId: string,
- payload: SubmitRepairUploadPayload,
- ): Promise => {
- const formData = new FormData();
- formData.append('defect_class', payload.defect_class);
- formData.append('notes', payload.notes);
- formData.append('video', payload.video);
- payload.images.forEach((image) => {
- formData.append('images', image);
- });
-
- const response = await axiosClient.post(
- API_ROUTES.TICKETS.SUBMIT_REPAIR_UPLOAD(ticketId),
- formData,
- {
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
- },
- );
- return response.data;
- },
-
requestTicketExtension: async (
ticketId: string,
assignmentId: number,
diff --git a/src/types/ticket/actions.ts b/src/types/ticket/actions.ts
index 0bb54f9..9c1485e 100644
--- a/src/types/ticket/actions.ts
+++ b/src/types/ticket/actions.ts
@@ -29,18 +29,6 @@ export interface AssignTicketPayload {
note?: string;
}
-export interface SubmitRepairPayload {
- notes: string;
- proof_path: string;
-}
-
-export interface SubmitRepairUploadPayload {
- defect_class: string;
- notes: string;
- video: File;
- images: File[];
-}
-
export interface RequestTicketExtensionPayload {
requested_due_at: string;
reason: string;