chore: ticket section implement sse
This commit is contained in:
@@ -7,13 +7,7 @@ import type { TicketDetail, TicketHistoryItem, TicketStatus } from '@/types';
|
||||
|
||||
import { formatTicketStatus } from '../../components/TicketStatusBadge';
|
||||
|
||||
function formatDate(value: string | null | undefined) {
|
||||
if (!value) return '-';
|
||||
return new Intl.DateTimeFormat('en-IN', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
}).format(new Date(value));
|
||||
}
|
||||
import { formatDate } from '@/utils/date';
|
||||
|
||||
function getActorLabel(item: TicketHistoryItem) {
|
||||
return item.actor?.name || item.actor?.email || 'System';
|
||||
|
||||
@@ -5,13 +5,7 @@ import type { TicketActor, TicketDetail } from '@/types';
|
||||
|
||||
import { TicketStatusBadge } from '../../components/TicketStatusBadge';
|
||||
|
||||
function formatDate(value: string | null | undefined) {
|
||||
if (!value) return '-';
|
||||
return new Intl.DateTimeFormat('en-IN', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
}).format(new Date(value));
|
||||
}
|
||||
import { formatDate } from '@/utils/date';
|
||||
|
||||
function OverviewMeta({
|
||||
label,
|
||||
|
||||
@@ -1,23 +1,65 @@
|
||||
'use client';
|
||||
|
||||
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 { NoTicketAction } from './actions/NoTicketAction';
|
||||
import { ProcessingTicketAction } from './actions/ProcessingTicketAction';
|
||||
import { ReviewRepairAction } from './actions/ReviewRepairAction';
|
||||
import { SubmitRepairAction } from './actions/SubmitRepairAction';
|
||||
|
||||
export function TicketStatusActions({ ticket }: { ticket: TicketDetail }) {
|
||||
interface TicketStatusActionsProps {
|
||||
ticket: TicketDetail;
|
||||
liveState: TicketDetailLiveState;
|
||||
}
|
||||
|
||||
export function TicketStatusActions({
|
||||
ticket,
|
||||
liveState,
|
||||
}: TicketStatusActionsProps) {
|
||||
if (ticket.status === 'processing') {
|
||||
return (
|
||||
<ProcessingTicketAction
|
||||
progress={liveState.progress}
|
||||
errorMessage={liveState.errorMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (ticket.status === 'unassigned') {
|
||||
return <AssignTicketAction ticket={ticket} />;
|
||||
return (
|
||||
<PermissionGuard
|
||||
permissions={PERMISSIONS.TICKET.ASSIGN}
|
||||
fallback={<NoTicketAction status={ticket.status} />}
|
||||
>
|
||||
<AssignTicketAction ticket={ticket} />
|
||||
</PermissionGuard>
|
||||
);
|
||||
}
|
||||
|
||||
if (ticket.status === 'assigned') {
|
||||
return <SubmitRepairAction ticket={ticket} />;
|
||||
return (
|
||||
<PermissionGuard
|
||||
permissions={PERMISSIONS.TICKET.WORK}
|
||||
fallback={<NoTicketAction status={ticket.status} />}
|
||||
>
|
||||
<SubmitRepairAction ticket={ticket} />
|
||||
</PermissionGuard>
|
||||
);
|
||||
}
|
||||
|
||||
if (ticket.status === 'under_review') {
|
||||
return <ReviewRepairAction ticket={ticket} />;
|
||||
return (
|
||||
<PermissionGuard
|
||||
permissions={PERMISSIONS.TICKET.REVIEW}
|
||||
fallback={<NoTicketAction status={ticket.status} />}
|
||||
>
|
||||
<ReviewRepairAction ticket={ticket} />
|
||||
</PermissionGuard>
|
||||
);
|
||||
}
|
||||
|
||||
return <NoTicketAction status={ticket.status} />;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
'use client';
|
||||
|
||||
import { AlertCircle, Loader2 } 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 (
|
||||
<TicketActionCard icon={Loader2} title="Analyzing Video">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between gap-3 text-sm">
|
||||
<span className="font-medium text-foreground">
|
||||
{getProgressMessage(progress)}
|
||||
</span>
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
{hasProgress ? `${Math.round(progressValue)}%` : '--'}
|
||||
</span>
|
||||
</div>
|
||||
<Progress value={hasProgress ? progressValue : 0} />
|
||||
</div>
|
||||
|
||||
{errorMessage && (
|
||||
<div className="flex gap-2 rounded-md border border-destructive/25 bg-destructive/10 p-3 text-sm text-destructive">
|
||||
<AlertCircle className="mt-0.5 size-4 shrink-0" />
|
||||
<span>{errorMessage}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TicketActionCard>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import { TicketHistoryCard } from './components/TicketHistoryCard';
|
||||
import { TicketOverviewCard } from './components/TicketOverviewCard';
|
||||
import { TicketRepairReportCard } from './components/TicketRepairReportCard';
|
||||
import { TicketStatusActions } from './components/TicketStatusActions';
|
||||
import { useTicketDetailEvents } from '../hooks/useTicketEvents';
|
||||
import { useTicketDetailEvents } from '../hooks/useTicketDetailEvents';
|
||||
import { useTicketDetailQuery } from '../hooks/useTicketQueries';
|
||||
|
||||
export default function TicketDetailPage() {
|
||||
@@ -20,7 +20,10 @@ export default function TicketDetailPage() {
|
||||
const ticketQuery = useTicketDetailQuery(ticketId);
|
||||
const ticket = ticketQuery.data;
|
||||
|
||||
useTicketDetailEvents(ticketId);
|
||||
const liveState = useTicketDetailEvents(
|
||||
ticketId,
|
||||
Boolean(ticketId && ticket && ticket.status !== 'closed'),
|
||||
);
|
||||
|
||||
if (ticketQuery.isLoading) {
|
||||
return (
|
||||
@@ -64,7 +67,7 @@ export default function TicketDetailPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_380px]">
|
||||
<div className="grid gap-5 xl:grid-cols-[minmax(0,1fr)_480px]">
|
||||
<div className="space-y-5">
|
||||
<TicketOverviewCard ticket={ticket} />
|
||||
<TicketRepairReportCard ticket={ticket} />
|
||||
@@ -72,7 +75,7 @@ export default function TicketDetailPage() {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TicketStatusActions ticket={ticket} />
|
||||
<TicketStatusActions ticket={ticket} liveState={liveState} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
Reference in New Issue
Block a user