diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/app/(modules)/upload/components/CardPagination.tsx b/src/app/(modules)/upload/components/CardPagination.tsx deleted file mode 100644 index f46e514..0000000 --- a/src/app/(modules)/upload/components/CardPagination.tsx +++ /dev/null @@ -1,79 +0,0 @@ -'use client'; - -import { ChevronLeft, ChevronRight } from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select'; - -const pageSizes = [10, 20, 40, 50, 100]; - -interface CardPaginationProps { - skip: number; - limit: number; - total: number; - count: number; - onPageChange: (skip: number) => void; - onLimitChange: (limit: number) => void; -} - -export function CardPagination({ - skip, - limit, - total, - count, - onPageChange, - onLimitChange, -}: CardPaginationProps) { - return ( -
-
- Showing {count} of {total} -
- Rows per page - -
-
- -
- - -
-
- ); -} diff --git a/src/app/(modules)/upload/components/UploadActionsMenu.tsx b/src/app/(modules)/upload/components/UploadActionsMenu.tsx index 8e7d964..b7e3f70 100644 --- a/src/app/(modules)/upload/components/UploadActionsMenu.tsx +++ b/src/app/(modules)/upload/components/UploadActionsMenu.tsx @@ -1,26 +1,33 @@ 'use client'; -import { Eye, MoreVertical, PlayCircle } from 'lucide-react'; +import { + Download, + Eye, + MoreVertical, + PlayCircle, + RotateCcw, + Ticket, + Trash2, +} from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, + DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import type { UploadVideo } from '@/types'; +import type { UploadListItem } from '@/types'; interface UploadActionsMenuProps { - upload: UploadVideo; - onViewVideo: (upload: UploadVideo) => void; - onViewDetails: (upload: UploadVideo) => void; + upload: UploadListItem; + onViewVideo: (upload: UploadListItem) => void; } export function UploadActionsMenu({ upload, onViewVideo, - onViewDetails, }: UploadActionsMenuProps) { return ( @@ -37,7 +44,7 @@ export function UploadActionsMenu({ - {upload.video_url ? ( + {upload.raw_video_url ? ( { event.stopPropagation(); @@ -48,14 +55,18 @@ export function UploadActionsMenu({ View Video ) : null} - { - event.stopPropagation(); - onViewDetails(upload); - }} - > - - View Details + + View Details + + + Retry Analysis + + + Open Ticket + + + + Delete diff --git a/src/app/(modules)/upload/components/UploadCardGrid.tsx b/src/app/(modules)/upload/components/UploadCardGrid.tsx deleted file mode 100644 index 4a25d07..0000000 --- a/src/app/(modules)/upload/components/UploadCardGrid.tsx +++ /dev/null @@ -1,107 +0,0 @@ -'use client'; - -import { FileVideo } from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; -import type { UploadVideo } from '@/types'; - -import { CardPagination } from './CardPagination'; -import { UploadActionsMenu } from './UploadActionsMenu'; -import { UploadThumbnail } from './UploadThumbnail'; -import { formatUploadedAt } from './uploadUtils'; - -interface UploadCardGridProps { - uploads: UploadVideo[]; - isLoading: boolean; - skip: number; - limit: number; - total: number; - onPageChange: (skip: number) => void; - onLimitChange: (limit: number) => void; - onUploadNew: () => void; - onOpenUpload: (upload: UploadVideo) => void; - onViewVideo: (upload: UploadVideo) => void; - onViewDetails: (upload: UploadVideo) => void; -} - -export function UploadCardGrid({ - uploads, - isLoading, - skip, - limit, - total, - onPageChange, - onLimitChange, - onUploadNew, - onOpenUpload, - onViewVideo, - onViewDetails, -}: UploadCardGridProps) { - const skeletonItems = Array.from({ length: 6 }); - - if (!isLoading && uploads.length === 0) { - return ( - - -
- -
-
-

No uploads found

-

- Upload your first road inspection video. -

-
- -
-
- ); - } - - return ( -
-
- {isLoading - ? skeletonItems.map((_, index) => ( - - )) - : uploads.map((upload) => ( - onOpenUpload(upload)} - > - - -
-
-

- {upload.filename} -

-

- Uploaded {formatUploadedAt(upload.uploaded_at)} -

-
- -
-
-
- ))} -
- - -
- ); -} diff --git a/src/app/(modules)/upload/components/UploadColumns.tsx b/src/app/(modules)/upload/components/UploadColumns.tsx deleted file mode 100644 index 8bbaa4a..0000000 --- a/src/app/(modules)/upload/components/UploadColumns.tsx +++ /dev/null @@ -1,77 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; -import type { ColumnDef } from '@tanstack/react-table'; - -import { UploadActionsMenu } from './UploadActionsMenu'; -import { UploadThumbnail } from './UploadThumbnail'; -import { formatUploadedAt, getUploadStatus } from './uploadUtils'; -import { Badge } from '@/components/ui/badge'; -import type { UploadVideo } from '@/types'; - -interface UseUploadColumnsProps { - onViewVideo: (upload: UploadVideo) => void; - onViewDetails: (upload: UploadVideo) => void; -} - -export function useUploadColumns({ - onViewVideo, - onViewDetails, -}: UseUploadColumnsProps) { - return useMemo[]>( - () => [ - { - id: 'thumbnail', - header: 'Thumbnail', - size: 140, - minSize: 120, - enableSorting: false, - meta: { disableTruncate: true }, - cell: ({ row }) => ( - - ), - }, - { - accessorKey: 'filename', - header: 'Filename', - cell: ({ row }) => ( - {row.original.filename} - ), - }, - { - id: 'status', - header: 'Status', - size: 130, - cell: ({ row }) => ( - - {getUploadStatus(row.original)} - - ), - }, - { - accessorKey: 'uploaded_at', - header: 'Uploaded At', - cell: ({ row }) => formatUploadedAt(row.original.uploaded_at), - }, - { - id: 'actions', - header: () => Actions, - size: 56, - minSize: 56, - enableSorting: false, - enableHiding: false, - meta: { disableTruncate: true }, - cell: ({ row }) => ( -
- -
- ), - }, - ], - [onViewDetails, onViewVideo], - ); -} diff --git a/src/app/(modules)/upload/components/UploadDetailsDialog.tsx b/src/app/(modules)/upload/components/UploadDetailsDialog.tsx deleted file mode 100644 index f67e51d..0000000 --- a/src/app/(modules)/upload/components/UploadDetailsDialog.tsx +++ /dev/null @@ -1,79 +0,0 @@ -'use client'; - -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; -import { Badge } from '@/components/ui/badge'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; -import type { UploadVideo } from '@/types'; - -import { formatUploadedAt, getUploadStatus } from './uploadUtils'; - -interface UploadDetailsDialogProps { - upload: UploadVideo | null; - open: boolean; - onOpenChange: (open: boolean) => void; -} - -export function UploadDetailsDialog({ - upload, - open, - onOpenChange, -}: UploadDetailsDialogProps) { - const title = upload?.filename ?? 'Upload Details'; - - return ( - - - - - {upload?.filename ? ( - - - - - {title} - - - - {title} - - - - ) : ( - title - )} - - Uploaded video details - - - {upload ? ( -
-
Status
-
- {getUploadStatus(upload)} -
-
Uploaded At
-
{formatUploadedAt(upload.uploaded_at)}
-
Uploaded By
-
{upload.uploaded_by_email ?? '-'}
-
Video ID
-
{upload.video_id}
-
- ) : null} -
-
- ); -} diff --git a/src/app/(modules)/upload/components/UploadListColumns.tsx b/src/app/(modules)/upload/components/UploadListColumns.tsx new file mode 100644 index 0000000..6feb189 --- /dev/null +++ b/src/app/(modules)/upload/components/UploadListColumns.tsx @@ -0,0 +1,153 @@ +'use client'; + +import { useMemo } from 'react'; +import type { ColumnDef } from '@tanstack/react-table'; +import { Video } from 'lucide-react'; + +import { ENV_CONSTANT } from '@/constants/secrect.constant'; +import type { UploadListItem } from '@/types'; +import { formatDate } from '@/utils/date'; + +import { UploadActionsMenu } from './UploadActionsMenu'; +import { UploadResultCell } from './UploadResultCell'; +import { UploadStatusBadge } from './UploadStatusBadge'; + +function resolveThumbnailUrl(value: string | null) { + if (!value) return null; + if (/^https?:\/\//i.test(value)) return value; + + const baseUrl = ENV_CONSTANT.BASE_API_URL?.replace(/\/$/, ''); + const path = value.startsWith('/') ? value : `/${value}`; + return baseUrl ? `${baseUrl}${path}` : path; +} + +export function useUploadListColumns( + onViewVideo: (upload: UploadListItem) => void, +): ColumnDef[] { + return useMemo( + () => [ + { + id: 'thumbnail', + header: 'Thumbnail', + size: 104, + minSize: 96, + enableSorting: false, + meta: { disableTruncate: true }, + cell: ({ row }) => { + const thumbnail = resolveThumbnailUrl(row.original.thumbnail); + + return ( +
+ {!thumbnail ? ( +
+ ); + }, + }, + { + accessorKey: 'video_name', + header: 'Video Name', + size: 150, + cell: ({ row }) => ( + {row.original.video_name} + ), + }, + { + id: 'uploaded_by', + header: 'Uploader', + size: 190, + cell: ({ row }) => ( +
+

+ {row.original.uploaded_by.name ?? '—'} +

+

+ {row.original.uploaded_by.email ?? '—'} +

+
+ ), + }, + { + accessorKey: 'project', + header: 'Project', + size: 165, + cell: ({ row }) => row.original.project ?? '—', + }, + { + accessorKey: 'package', + header: 'Package', + size: 110, + cell: ({ row }) => row.original.package ?? '—', + }, + { + accessorKey: 'segment', + header: 'Segment', + size: 140, + cell: ({ row }) => row.original.segment ?? '—', + }, + { + accessorKey: 'chainage', + header: 'Chainage', + size: 155, + cell: ({ row }) => row.original.chainage ?? '—', + }, + { + accessorKey: 'uploaded_at', + header: 'Uploaded At', + size: 175, + cell: ({ row }) => formatDate(row.original.uploaded_at), + }, + { + id: 'status', + header: 'Status', + size: 145, + meta: { disableTruncate: true }, + cell: ({ row }) => ( + + ), + }, + { + id: 'result', + header: 'Result', + size: 170, + meta: { disableTruncate: true }, + cell: ({ row }) => , + }, + { + id: 'actions', + header: () => Actions, + size: 64, + minSize: 64, + enableSorting: false, + enableHiding: false, + meta: { disableTruncate: true }, + cell: ({ row }) => ( +
+ +
+ ), + }, + ], + [onViewVideo], + ); +} diff --git a/src/app/(modules)/upload/components/UploadResultCell.tsx b/src/app/(modules)/upload/components/UploadResultCell.tsx new file mode 100644 index 0000000..3344ae6 --- /dev/null +++ b/src/app/(modules)/upload/components/UploadResultCell.tsx @@ -0,0 +1,32 @@ +import { CircleX, TicketCheck } from 'lucide-react'; + +import { Badge } from '@/components/ui/badge'; +import type { UploadListItem } from '@/types'; + +export function UploadResultCell({ item }: { item: UploadListItem }) { + if (item.result === 'ticket_created') { + return ( +
+ + Ticket Created + +

{item.ticket_id}

+
+ ); + } + + if (item.result === 'analysis_failed') { + return ( +
+
+ Analysis Failed +
+

+ {item.error_message ?? 'Processing failed'} +

+
+ ); + } + + return ; +} diff --git a/src/app/(modules)/upload/components/UploadStatusBadge.tsx b/src/app/(modules)/upload/components/UploadStatusBadge.tsx new file mode 100644 index 0000000..445b7f6 --- /dev/null +++ b/src/app/(modules)/upload/components/UploadStatusBadge.tsx @@ -0,0 +1,65 @@ +import { CheckCircle2, Clock3, LoaderCircle, XCircle } from 'lucide-react'; + +import { Badge } from '@/components/ui/badge'; +import { Progress } from '@/components/ui/progress'; +import type { UploadStatus } from '@/types'; + +interface UploadStatusBadgeProps { + status: UploadStatus; + progress: number; +} + +const statusConfig = { + queued: { + label: 'Queued', + icon: Clock3, + className: 'bg-zinc-500/15 text-zinc-500 dark:text-zinc-300', + }, + processing: { + label: 'Processing', + icon: LoaderCircle, + className: 'bg-blue-500/15 text-blue-600 dark:text-blue-400', + }, + completed: { + label: 'Completed', + icon: CheckCircle2, + className: 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400', + }, + failed: { + label: 'Failed', + icon: XCircle, + className: 'bg-red-500/15 text-red-600 dark:text-red-400', + }, +} satisfies Record< + UploadStatus, + { label: string; icon: typeof Clock3; className: string } +>; + +export function UploadStatusBadge({ + status, + progress, +}: UploadStatusBadgeProps) { + const config = statusConfig[status]; + const Icon = config.icon; + + return ( +
+ + + {config.label} + + {status === 'processing' ? ( +
+ + {progress}% complete + + +
+ ) : status === 'completed' ? ( +

100%

+ ) : null} +
+ ); +} diff --git a/src/app/(modules)/upload/components/UploadTable.tsx b/src/app/(modules)/upload/components/UploadTable.tsx index 70ee80f..524a62e 100644 --- a/src/app/(modules)/upload/components/UploadTable.tsx +++ b/src/app/(modules)/upload/components/UploadTable.tsx @@ -3,18 +3,17 @@ import type { ColumnDef } from '@tanstack/react-table'; import { DataTable } from '@/components/data-table'; -import type { UploadVideo } from '@/types'; +import type { UploadListItem } from '@/types'; interface UploadTableProps { - columns: ColumnDef[]; - uploads: UploadVideo[]; + columns: ColumnDef[]; + uploads: UploadListItem[]; isLoading: boolean; skip: number; limit: number; total: number; onPageChange: (skip: number) => void; onLimitChange: (limit: number) => void; - onRowClick: (upload: UploadVideo) => void; } export function UploadTable({ @@ -26,7 +25,6 @@ export function UploadTable({ total, onPageChange, onLimitChange, - onRowClick, }: UploadTableProps) { return ( diff --git a/src/app/(modules)/upload/components/UploadThumbnail.tsx b/src/app/(modules)/upload/components/UploadThumbnail.tsx deleted file mode 100644 index 2b4608e..0000000 --- a/src/app/(modules)/upload/components/UploadThumbnail.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { ImageIcon } from 'lucide-react'; - -import type { UploadVideo } from '@/types'; - -import { resolveMediaUrl } from './uploadUtils'; - -interface UploadThumbnailProps { - upload: UploadVideo; - className?: string; -} - -export function UploadThumbnail({ upload, className }: UploadThumbnailProps) { - const thumbnailUrl = resolveMediaUrl(upload.thumbnail); - - return ( -
-
- {thumbnailUrl ? ( - {upload.filename} - ) : ( -
- -
- )} -
-
- ); -} diff --git a/src/app/(modules)/upload/components/UploadVideoDialog.tsx b/src/app/(modules)/upload/components/UploadVideoDialog.tsx index 6ca09b4..4312741 100644 --- a/src/app/(modules)/upload/components/UploadVideoDialog.tsx +++ b/src/app/(modules)/upload/components/UploadVideoDialog.tsx @@ -8,10 +8,10 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; -import type { UploadVideo } from '@/types'; +import type { UploadListItem } from '@/types'; interface UploadVideoDialogProps { - upload: UploadVideo | null; + upload: UploadListItem | null; open: boolean; onOpenChange: (open: boolean) => void; } @@ -29,12 +29,12 @@ export function UploadVideoDialog({ Video Preview - {upload?.filename ?? 'Uploaded video'} + {upload?.video_name ?? 'Uploaded video'} diff --git a/src/app/(modules)/upload/components/ViewSwitcher.tsx b/src/app/(modules)/upload/components/ViewSwitcher.tsx deleted file mode 100644 index 3e277df..0000000 --- a/src/app/(modules)/upload/components/ViewSwitcher.tsx +++ /dev/null @@ -1,46 +0,0 @@ -'use client'; - -import { Grid2X2, Table2 } from 'lucide-react'; - -import { Button } from '@/components/ui/button'; -import { cn } from '@/lib/utils'; - -import type { UploadViewMode } from '../hooks/useUploadFilters'; - -interface ViewSwitcherProps { - value: UploadViewMode; - onValueChange: (value: UploadViewMode) => void; -} - -export function ViewSwitcher({ value, onValueChange }: ViewSwitcherProps) { - const items: Array<{ - value: UploadViewMode; - label: string; - icon: React.ReactNode; - }> = [ - { value: 'card', label: 'Card View', icon: }, - { value: 'table', label: 'Table View', icon: }, - ]; - - return ( -
- {items.map((item) => ( - - ))} -
- ); -} - diff --git a/src/app/(modules)/upload/components/uploadUtils.ts b/src/app/(modules)/upload/components/uploadUtils.ts deleted file mode 100644 index 3106fa6..0000000 --- a/src/app/(modules)/upload/components/uploadUtils.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { ENV_CONSTANT } from '@/constants/secrect.constant'; -import type { UploadVideo } from '@/types'; - -export function getUploadStatus(upload: UploadVideo) { - return upload.status?.status || 'unknown'; -} - -export function formatUploadedAt(value: string) { - const date = new Date(value); - if (Number.isNaN(date.getTime())) return '-'; - - return new Intl.DateTimeFormat('en-IN', { - day: '2-digit', - month: 'short', - year: 'numeric', - hour: 'numeric', - minute: '2-digit', - }).format(date); -} - -export function resolveMediaUrl(url?: string | null) { - if (!url) return null; - if (/^https?:\/\//i.test(url)) return url; - - const baseUrl = ENV_CONSTANT.BASE_API_URL?.replace(/\/$/, ''); - const path = url.startsWith('/') ? url : `/${url}`; - return baseUrl ? `${baseUrl}${path}` : path; -} - diff --git a/src/app/(modules)/upload/hooks/useUploadFilters.ts b/src/app/(modules)/upload/hooks/useUploadFilters.ts index 6f89585..f034bb7 100644 --- a/src/app/(modules)/upload/hooks/useUploadFilters.ts +++ b/src/app/(modules)/upload/hooks/useUploadFilters.ts @@ -2,14 +2,11 @@ import { useState } from 'react'; -export type UploadViewMode = 'card' | 'table'; - const DEFAULT_LIMIT = 20; export function useUploadFilters() { const [skip, setSkip] = useState(0); const [limit, setLimitValue] = useState(DEFAULT_LIMIT); - const [viewMode, setViewMode] = useState('card'); const setLimit = (nextLimit: number) => { setLimitValue(nextLimit); @@ -21,7 +18,5 @@ export function useUploadFilters() { setSkip, limit, setLimit, - viewMode, - setViewMode, }; } diff --git a/src/app/(modules)/upload/hooks/useUploadQueries.ts b/src/app/(modules)/upload/hooks/useUploadQueries.ts index e4876ee..26f59ad 100644 --- a/src/app/(modules)/upload/hooks/useUploadQueries.ts +++ b/src/app/(modules)/upload/hooks/useUploadQueries.ts @@ -13,7 +13,7 @@ export const uploadKeys = { list: (params: PaginationParams) => [...uploadKeys.lists(), params] as const, }; -interface UseMyUploadsQueryParams { +interface UseUploadsQueryParams { skip: number; limit: number; } @@ -21,14 +21,14 @@ interface UseMyUploadsQueryParams { function buildUploadListParams({ skip, limit, -}: UseMyUploadsQueryParams): PaginationParams { +}: UseUploadsQueryParams): PaginationParams { return { skip, limit, }; } -export function useMyUploadsQuery(params: UseMyUploadsQueryParams) { +export function useUploadsQuery(params: UseUploadsQueryParams) { const { skip, limit } = params; const listParams = useMemo( () => buildUploadListParams({ skip, limit }), @@ -37,7 +37,7 @@ export function useMyUploadsQuery(params: UseMyUploadsQueryParams) { return useQuery({ queryKey: uploadKeys.list(listParams), - queryFn: () => videoService.getMyUploads(listParams), + queryFn: () => videoService.getUploads(listParams), }); } diff --git a/src/app/(modules)/upload/page.tsx b/src/app/(modules)/upload/page.tsx index 6f44623..d4ff595 100644 --- a/src/app/(modules)/upload/page.tsx +++ b/src/app/(modules)/upload/page.tsx @@ -1,62 +1,37 @@ 'use client'; -import { useCallback, useState } from 'react'; +import { useState } from 'react'; import { Plus, UploadCloud } from 'lucide-react'; import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; -import type { UploadVideo } from '@/types'; +import type { UploadListItem } from '@/types'; import { CreateUploadDialog } from './components/CreateUploadDialog'; -import { UploadCardGrid } from './components/UploadCardGrid'; -import { useUploadColumns } from './components/UploadColumns'; -import { UploadDetailsDialog } from './components/UploadDetailsDialog'; +import { useUploadListColumns } from './components/UploadListColumns'; import { UploadTable } from './components/UploadTable'; import { UploadVideoDialog } from './components/UploadVideoDialog'; -import { ViewSwitcher } from './components/ViewSwitcher'; import { useUploadFilters } from './hooks/useUploadFilters'; -import { useMyUploadsQuery } from './hooks/useUploadQueries'; +import { useUploadsQuery } from './hooks/useUploadQueries'; export default function UploadPage() { - const { skip, setSkip, limit, setLimit, viewMode, setViewMode } = - useUploadFilters(); - const uploadsQuery = useMyUploadsQuery({ skip, limit }); + const { skip, setSkip, limit, setLimit } = useUploadFilters(); + const uploadsQuery = useUploadsQuery({ skip, limit }); const [isCreateOpen, setIsCreateOpen] = useState(false); - const [detailsUpload, setDetailsUpload] = useState(null); - const [videoUpload, setVideoUpload] = useState(null); + const [videoUpload, setVideoUpload] = useState(null); const uploads = uploadsQuery.data?.items ?? []; const total = uploadsQuery.data?.total ?? 0; const isLoading = uploadsQuery.isLoading; - const handleViewDetails = useCallback((upload: UploadVideo) => { - setDetailsUpload(upload); - }, []); - - const handleViewUploadVideo = useCallback((upload: UploadVideo) => { - setVideoUpload(upload); - }, []); - - const handleOpenUpload = useCallback( - (upload: UploadVideo) => { - if (upload.video_url) { - handleViewUploadVideo(upload); - } - }, - [handleViewUploadVideo], - ); - - const columns = useUploadColumns({ - onViewVideo: handleViewUploadVideo, - onViewDetails: handleViewDetails, - }); + const columns = useUploadListColumns(setVideoUpload); return ( <>
setIsCreateOpen(true)} size="sm"> @@ -66,47 +41,19 @@ export default function UploadPage() { } /> -
- -
- - {viewMode === 'card' ? ( - setIsCreateOpen(true)} - onOpenUpload={handleOpenUpload} - onViewVideo={handleViewUploadVideo} - onViewDetails={handleViewDetails} - /> - ) : ( - - )} +
- { - if (!open) setDetailsUpload(null); - }} - /> boolean, fallback = ROUTES.ACCESS, ) { - return findFirstMenuPath(filterMenuItems(menuItems, hasPermission)) ?? fallback; + return ( + findFirstMenuPath(filterMenuItems(menuItems, hasPermission)) ?? fallback + ); } export function getFirstAccessiblePathForPermissions( diff --git a/src/constants/apiRoutes.ts b/src/constants/apiRoutes.ts index 700d23f..788e31d 100644 --- a/src/constants/apiRoutes.ts +++ b/src/constants/apiRoutes.ts @@ -49,7 +49,7 @@ export const API_ROUTES = { }, VIDEOS: { UPLOAD: '/biz/api/v1/upload', - MY_UPLOADS: '/biz/api/v1/videos/me', + UPLOADS: '/biz/api/v1/uploads', RESULTS: (id: string) => `/biz/api/v1/results/${id}/completed`, }, TICKETS: { diff --git a/src/services/api/video.service.ts b/src/services/api/video.service.ts index eca55b0..e172bbd 100644 --- a/src/services/api/video.service.ts +++ b/src/services/api/video.service.ts @@ -1,16 +1,20 @@ import axiosClient from '../axios/axios'; import { API_ROUTES } from '@/constants/apiRoutes'; -import { CompletedVideoResult, PaginationParams, UploadVideosResponse } from '@/types'; +import { + CompletedVideoResult, + PaginationParams, + UploadListResponse, +} from '@/types'; /** * Video Service */ export const videoService = { - getMyUploads: async ( + getUploads: async ( params?: PaginationParams, - ): Promise => { - const response = await axiosClient.get( - API_ROUTES.VIDEOS.MY_UPLOADS, + ): Promise => { + const response = await axiosClient.get( + API_ROUTES.VIDEOS.UPLOADS, { params: { skip: params?.skip ?? 0, diff --git a/src/types/upload.ts b/src/types/upload.ts index fce6cc8..edf0aef 100644 --- a/src/types/upload.ts +++ b/src/types/upload.ts @@ -1,25 +1,25 @@ -export interface UploadVideoStatus { - status: string; +export type UploadStatus = 'queued' | 'processing' | 'completed' | 'failed'; +export type UploadResult = 'ticket_created' | 'analysis_failed' | null; + +export interface UploadListItem { + id: string; + video_name: string; + thumbnail: string | null; + raw_video_url: string | null; + uploaded_by: { name: string | null; email: string | null }; + project: string | null; + package: string | null; + segment: string | null; + chainage: string | null; + uploaded_at: string | null; + status: UploadStatus; progress: number; + result: UploadResult; + ticket_id: string | null; + error_message: string | null; } -export interface UploadVideo { - video_id: string; - filename: string; - chainage_id: string; - detection_mode: string; - speed_kmh?: number | null; - thumbnail?: string | null; - uploaded_at: string; - uploaded_by_user_id?: number | null; - uploaded_by_email?: string | null; - video_url?: string | null; - processed_video_path?: string | null; - processed_video_url?: string | null; - status: UploadVideoStatus; -} - -export interface UploadVideosResponse { - items: UploadVideo[]; +export interface UploadListResponse { + items: UploadListItem[]; total: number; }