chore: ticket section implement sse

This commit is contained in:
2026-06-19 18:06:11 +05:30
parent a567a52e26
commit fffd8e3e12
25 changed files with 711 additions and 307 deletions

View File

@@ -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';

View File

@@ -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,

View File

@@ -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} />;

View File

@@ -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>
);
}