feat(ticket): add shared extension request history for workers and managers
This commit is contained in:
@@ -43,19 +43,32 @@ function getTicketAction(
|
|||||||
ticketId: string,
|
ticketId: string,
|
||||||
assignment?: TicketAssignmentSummary,
|
assignment?: TicketAssignmentSummary,
|
||||||
) {
|
) {
|
||||||
if (assignment && !assignment.is_complete) {
|
if (assignment) {
|
||||||
return (
|
return (
|
||||||
<PermissionGuard
|
<PermissionGuard
|
||||||
permissions={PERMISSIONS.TICKET.ASSIGN}
|
permissions={PERMISSIONS.TICKET.ASSIGN}
|
||||||
fallback={
|
fallback={
|
||||||
<PermissionGuard
|
<PermissionGuard
|
||||||
permissions={PERMISSIONS.TICKET.WORK}
|
permissions={PERMISSIONS.TICKET.WORK}
|
||||||
fallback={<NoTicketAction />}
|
fallback={
|
||||||
|
<PermissionGuard
|
||||||
|
permissions={PERMISSIONS.TICKET.READ}
|
||||||
|
fallback={<NoTicketAction />}
|
||||||
|
>
|
||||||
|
<RequestExtensionAction
|
||||||
|
ticketId={ticketId}
|
||||||
|
assignmentId={assignment.id}
|
||||||
|
dueAt={assignment.due_at}
|
||||||
|
canRequest={false}
|
||||||
|
/>
|
||||||
|
</PermissionGuard>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<RequestExtensionAction
|
<RequestExtensionAction
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
assignmentId={assignment.id}
|
assignmentId={assignment.id}
|
||||||
dueAt={assignment.due_at}
|
dueAt={assignment.due_at}
|
||||||
|
canRequest={!assignment.is_complete}
|
||||||
/>
|
/>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
}
|
}
|
||||||
@@ -63,6 +76,7 @@ function getTicketAction(
|
|||||||
<ReviewExtensionRequestAction
|
<ReviewExtensionRequestAction
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
assignmentId={assignment.id}
|
assignmentId={assignment.id}
|
||||||
|
canReview={!assignment.is_complete}
|
||||||
/>
|
/>
|
||||||
</PermissionGuard>
|
</PermissionGuard>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,162 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { ArrowRight, History } from 'lucide-react';
|
||||||
|
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogBody,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from '@/components/ui/dialog';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import type {
|
||||||
|
TicketExtensionRequest,
|
||||||
|
TicketExtensionRequestStatus,
|
||||||
|
} from '@/types';
|
||||||
|
import { formatDate } from '@/utils/date';
|
||||||
|
|
||||||
|
const statusConfig: Record<
|
||||||
|
TicketExtensionRequestStatus,
|
||||||
|
{ label: string; className: string }
|
||||||
|
> = {
|
||||||
|
pending: {
|
||||||
|
label: 'Pending',
|
||||||
|
className:
|
||||||
|
'border-amber-200 bg-amber-100 text-amber-800 dark:border-amber-900 dark:bg-amber-950/50 dark:text-amber-300',
|
||||||
|
},
|
||||||
|
approved: {
|
||||||
|
label: 'Approved',
|
||||||
|
className:
|
||||||
|
'border-emerald-200 bg-emerald-100 text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950/50 dark:text-emerald-300',
|
||||||
|
},
|
||||||
|
rejected: {
|
||||||
|
label: 'Rejected',
|
||||||
|
className:
|
||||||
|
'border-red-200 bg-red-100 text-red-800 dark:border-red-900 dark:bg-red-950/50 dark:text-red-300',
|
||||||
|
},
|
||||||
|
cancelled: {
|
||||||
|
label: 'Cancelled',
|
||||||
|
className: 'border-border bg-muted text-muted-foreground',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function HistoryItem({ request }: { request: TicketExtensionRequest }) {
|
||||||
|
const status = statusConfig[request.status];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<article className="space-y-3 rounded-lg border p-4">
|
||||||
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||||
|
<Badge variant="outline" className={cn('border', status.className)}>
|
||||||
|
{status.label}
|
||||||
|
</Badge>
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
Requested {formatDate(request.requested_at)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Requested By</p>
|
||||||
|
<p className="mt-0.5 text-sm font-medium">
|
||||||
|
{request.requested_by?.name ?? 'Assigned worker'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-start gap-3 rounded-lg bg-muted/40 p-3">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Previous Due Date</p>
|
||||||
|
<p className="mt-1 text-sm font-medium">
|
||||||
|
{formatDate(request.current_due_at)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ArrowRight className="mt-5 size-4 text-muted-foreground" />
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Requested Due Date</p>
|
||||||
|
<p className="mt-1 text-sm font-medium">
|
||||||
|
{formatDate(request.requested_due_at)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Reason</p>
|
||||||
|
<p className="mt-1 text-sm leading-relaxed">{request.reason}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{request.resolved_at || request.resolved_by || request.decision_note ? (
|
||||||
|
<div className="space-y-2 border-t pt-3">
|
||||||
|
<p className="text-xs font-semibold">Manager Response</p>
|
||||||
|
<div className="grid gap-2 text-sm sm:grid-cols-2">
|
||||||
|
{request.resolved_by ? (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Resolved By</p>
|
||||||
|
<p className="mt-0.5 font-medium">
|
||||||
|
{request.resolved_by.name} · {request.resolved_by.role.name}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{request.resolved_at ? (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Resolved On</p>
|
||||||
|
<p className="mt-0.5 font-medium">
|
||||||
|
{formatDate(request.resolved_at)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
{request.decision_note ? (
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground">Manager Comments</p>
|
||||||
|
<p className="mt-1 text-sm leading-relaxed">
|
||||||
|
{request.decision_note}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExtensionRequestHistoryDialog({
|
||||||
|
requests,
|
||||||
|
}: {
|
||||||
|
requests: TicketExtensionRequest[];
|
||||||
|
}) {
|
||||||
|
const sortedRequests = [...requests].sort(
|
||||||
|
(left, right) =>
|
||||||
|
new Date(right.requested_at).getTime() -
|
||||||
|
new Date(left.requested_at).getTime(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button type="button" variant="outline" className="w-full">
|
||||||
|
<History />
|
||||||
|
View Request History ({requests.length})
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Extension Request History</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Review every deadline extension request and manager decision for
|
||||||
|
this ticket lot.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogBody className="space-y-3">
|
||||||
|
{sortedRequests.map((request) => (
|
||||||
|
<HistoryItem key={request.id} request={request} />
|
||||||
|
))}
|
||||||
|
</DialogBody>
|
||||||
|
<DialogFooter showCloseButton />
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,21 +7,24 @@ export function RequestExtensionAction({
|
|||||||
ticketId,
|
ticketId,
|
||||||
assignmentId,
|
assignmentId,
|
||||||
dueAt,
|
dueAt,
|
||||||
|
canRequest,
|
||||||
}: {
|
}: {
|
||||||
ticketId: string;
|
ticketId: string;
|
||||||
assignmentId: number;
|
assignmentId: number;
|
||||||
dueAt: string | null;
|
dueAt: string | null;
|
||||||
|
canRequest: boolean;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<TicketActionCard
|
<TicketActionCard
|
||||||
icon={Clock3}
|
icon={Clock3}
|
||||||
title="Request Extension"
|
title={canRequest ? 'Request Extension' : 'Extension Request History'}
|
||||||
contextLabel={`Lot ${assignmentId}`}
|
contextLabel={`Lot ${assignmentId}`}
|
||||||
>
|
>
|
||||||
<TicketExtensionRequestPanel
|
<TicketExtensionRequestPanel
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
assignmentId={assignmentId}
|
assignmentId={assignmentId}
|
||||||
dueAt={dueAt}
|
dueAt={dueAt}
|
||||||
|
canRequest={canRequest}
|
||||||
/>
|
/>
|
||||||
</TicketActionCard>
|
</TicketActionCard>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ import {
|
|||||||
useExtensionRequestDecision,
|
useExtensionRequestDecision,
|
||||||
} from '../../../../extension-requests/hooks/useExtensionRequestDecision';
|
} from '../../../../extension-requests/hooks/useExtensionRequestDecision';
|
||||||
import { useTicketExtensionRequestQuery } from '../../../hooks/useTicketQueries';
|
import { useTicketExtensionRequestQuery } from '../../../hooks/useTicketQueries';
|
||||||
|
import { ExtensionRequestHistoryDialog } from './ExtensionRequestHistoryDialog';
|
||||||
|
import {
|
||||||
|
ExtensionRequestDetails,
|
||||||
|
getLatestRequest,
|
||||||
|
} from './TicketExtensionRequestPanel';
|
||||||
import { TicketActionCard } from './TicketActionCard';
|
import { TicketActionCard } from './TicketActionCard';
|
||||||
|
|
||||||
function getPendingRequest(response: TicketExtensionRequestsResponse) {
|
function getPendingRequest(response: TicketExtensionRequestsResponse) {
|
||||||
@@ -86,10 +91,12 @@ function ExtensionReviewForm({
|
|||||||
ticketId,
|
ticketId,
|
||||||
assignmentId,
|
assignmentId,
|
||||||
request,
|
request,
|
||||||
|
requests,
|
||||||
}: {
|
}: {
|
||||||
ticketId: string;
|
ticketId: string;
|
||||||
assignmentId: number;
|
assignmentId: number;
|
||||||
request: TicketExtensionRequest;
|
request: TicketExtensionRequest;
|
||||||
|
requests: TicketExtensionRequest[];
|
||||||
}) {
|
}) {
|
||||||
const {
|
const {
|
||||||
note,
|
note,
|
||||||
@@ -210,6 +217,8 @@ function ExtensionReviewForm({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ExtensionRequestHistoryDialog requests={requests} />
|
||||||
|
|
||||||
<AlertDialog
|
<AlertDialog
|
||||||
open={isRejectConfirmationOpen}
|
open={isRejectConfirmationOpen}
|
||||||
onOpenChange={setIsRejectConfirmationOpen}
|
onOpenChange={setIsRejectConfirmationOpen}
|
||||||
@@ -242,9 +251,11 @@ function ExtensionReviewForm({
|
|||||||
export function ReviewExtensionRequestAction({
|
export function ReviewExtensionRequestAction({
|
||||||
ticketId,
|
ticketId,
|
||||||
assignmentId,
|
assignmentId,
|
||||||
|
canReview,
|
||||||
}: {
|
}: {
|
||||||
ticketId: string;
|
ticketId: string;
|
||||||
assignmentId: number;
|
assignmentId: number;
|
||||||
|
canReview: boolean;
|
||||||
}) {
|
}) {
|
||||||
const extensionRequestQuery = useTicketExtensionRequestQuery(
|
const extensionRequestQuery = useTicketExtensionRequestQuery(
|
||||||
ticketId,
|
ticketId,
|
||||||
@@ -274,13 +285,32 @@ export function ReviewExtensionRequestAction({
|
|||||||
? getPendingRequest(extensionRequestQuery.data)
|
? getPendingRequest(extensionRequestQuery.data)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!pendingRequest) return null;
|
const requests = extensionRequestQuery.data?.items ?? [];
|
||||||
|
|
||||||
|
if (!pendingRequest || !canReview) {
|
||||||
|
const latestRequest = extensionRequestQuery.data
|
||||||
|
? getLatestRequest(extensionRequestQuery.data)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!latestRequest) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TicketActionCard
|
||||||
|
icon={CalendarClock}
|
||||||
|
title="Extension Request"
|
||||||
|
contextLabel={`Lot ${assignmentId}`}
|
||||||
|
>
|
||||||
|
<ExtensionRequestDetails request={latestRequest} requests={requests} />
|
||||||
|
</TicketActionCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExtensionReviewForm
|
<ExtensionReviewForm
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
assignmentId={assignmentId}
|
assignmentId={assignmentId}
|
||||||
request={pendingRequest}
|
request={pendingRequest}
|
||||||
|
requests={requests}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CalendarDays,
|
CalendarDays,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Clock3,
|
Clock3,
|
||||||
History,
|
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Plus,
|
||||||
RotateCcw,
|
RotateCcw,
|
||||||
XCircle,
|
XCircle,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
@@ -20,6 +21,7 @@ import type {
|
|||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
|
|
||||||
import { useTicketExtensionRequestQuery } from '../../../hooks/useTicketQueries';
|
import { useTicketExtensionRequestQuery } from '../../../hooks/useTicketQueries';
|
||||||
|
import { ExtensionRequestHistoryDialog } from './ExtensionRequestHistoryDialog';
|
||||||
import { RequestExtensionForm } from './RequestExtensionForm';
|
import { RequestExtensionForm } from './RequestExtensionForm';
|
||||||
|
|
||||||
interface DetailRowProps {
|
interface DetailRowProps {
|
||||||
@@ -44,21 +46,21 @@ function DetailRow({ label, value, children, className }: DetailRowProps) {
|
|||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
pending: {
|
pending: {
|
||||||
title: 'Request Pending',
|
title: 'Request Pending',
|
||||||
description: 'Your extension request is waiting for manager approval.',
|
description: 'This extension request is waiting for manager approval.',
|
||||||
icon: Clock3,
|
icon: Clock3,
|
||||||
className:
|
className:
|
||||||
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/30 dark:text-amber-300',
|
'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/30 dark:text-amber-300',
|
||||||
},
|
},
|
||||||
approved: {
|
approved: {
|
||||||
title: 'Request Approved',
|
title: 'Request Approved',
|
||||||
description: 'Your extension request has been approved by the manager.',
|
description: 'This extension request was approved by the manager.',
|
||||||
icon: CheckCircle2,
|
icon: CheckCircle2,
|
||||||
className:
|
className:
|
||||||
'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/60 dark:bg-emerald-950/30 dark:text-emerald-300',
|
'border-emerald-200 bg-emerald-50 text-emerald-700 dark:border-emerald-900/60 dark:bg-emerald-950/30 dark:text-emerald-300',
|
||||||
},
|
},
|
||||||
rejected: {
|
rejected: {
|
||||||
title: 'Request Rejected',
|
title: 'Request Rejected',
|
||||||
description: 'Your extension request has been rejected by the manager.',
|
description: 'This extension request was rejected by the manager.',
|
||||||
icon: AlertCircle,
|
icon: AlertCircle,
|
||||||
className:
|
className:
|
||||||
'border-red-200 bg-red-50 text-red-700 dark:border-red-900/60 dark:bg-red-950/30 dark:text-red-300',
|
'border-red-200 bg-red-50 text-red-700 dark:border-red-900/60 dark:bg-red-950/30 dark:text-red-300',
|
||||||
@@ -79,7 +81,7 @@ const statusConfig = {
|
|||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
|
||||||
function getLatestRequest(response: TicketExtensionRequestsResponse) {
|
export function getLatestRequest(response: TicketExtensionRequestsResponse) {
|
||||||
return response.items.reduce<TicketExtensionRequest | null>(
|
return response.items.reduce<TicketExtensionRequest | null>(
|
||||||
(latest, item) => {
|
(latest, item) => {
|
||||||
if (!latest) return item;
|
if (!latest) return item;
|
||||||
@@ -98,10 +100,14 @@ function getExtensionDays(request: TicketExtensionRequest) {
|
|||||||
return Math.max(0, Math.ceil(difference / (24 * 60 * 60 * 1000)));
|
return Math.max(0, Math.ceil(difference / (24 * 60 * 60 * 1000)));
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExtensionRequestDetails({
|
export function ExtensionRequestDetails({
|
||||||
request,
|
request,
|
||||||
|
requests,
|
||||||
|
onRequestAnother,
|
||||||
}: {
|
}: {
|
||||||
request: TicketExtensionRequest;
|
request: TicketExtensionRequest;
|
||||||
|
requests: TicketExtensionRequest[];
|
||||||
|
onRequestAnother?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const config = statusConfig[request.status];
|
const config = statusConfig[request.status];
|
||||||
const StatusIcon = config.icon;
|
const StatusIcon = config.icon;
|
||||||
@@ -124,6 +130,10 @@ function ExtensionRequestDetails({
|
|||||||
label="Requested On"
|
label="Requested On"
|
||||||
value={formatDate(request.requested_at)}
|
value={formatDate(request.requested_at)}
|
||||||
/>
|
/>
|
||||||
|
<DetailRow
|
||||||
|
label="Requested By"
|
||||||
|
value={request.requested_by?.name ?? 'Assigned worker'}
|
||||||
|
/>
|
||||||
<DetailRow
|
<DetailRow
|
||||||
label="Requested Due Date"
|
label="Requested Due Date"
|
||||||
value={formatDate(request.requested_due_at)}
|
value={formatDate(request.requested_due_at)}
|
||||||
@@ -160,6 +170,12 @@ function ExtensionRequestDetails({
|
|||||||
value={formatDate(request.requested_due_at)}
|
value={formatDate(request.requested_due_at)}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
{request.decision_note ? (
|
||||||
|
<DetailRow
|
||||||
|
label="Manager Comments"
|
||||||
|
value={request.decision_note}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -176,10 +192,15 @@ function ExtensionRequestDetails({
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<Button type="button" variant="outline" className="w-full">
|
<div className="space-y-2">
|
||||||
<History />
|
<ExtensionRequestHistoryDialog requests={requests} />
|
||||||
View Approval History
|
{request.status !== 'pending' && onRequestAnother ? (
|
||||||
</Button>
|
<Button type="button" className="w-full" onClick={onRequestAnother}>
|
||||||
|
<Plus />
|
||||||
|
Request Another Extension
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -188,11 +209,14 @@ export function TicketExtensionRequestPanel({
|
|||||||
ticketId,
|
ticketId,
|
||||||
assignmentId,
|
assignmentId,
|
||||||
dueAt,
|
dueAt,
|
||||||
|
canRequest,
|
||||||
}: {
|
}: {
|
||||||
ticketId: string;
|
ticketId: string;
|
||||||
assignmentId: number;
|
assignmentId: number;
|
||||||
dueAt: string | null;
|
dueAt: string | null;
|
||||||
|
canRequest: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
const [isRequestFormOpen, setIsRequestFormOpen] = useState(false);
|
||||||
const extensionRequestQuery = useTicketExtensionRequestQuery(
|
const extensionRequestQuery = useTicketExtensionRequestQuery(
|
||||||
ticketId,
|
ticketId,
|
||||||
assignmentId,
|
assignmentId,
|
||||||
@@ -231,6 +255,14 @@ export function TicketExtensionRequestPanel({
|
|||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (!latestRequest) {
|
if (!latestRequest) {
|
||||||
|
if (!canRequest) {
|
||||||
|
return (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No extension request history is available for this ticket lot.
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RequestExtensionForm
|
<RequestExtensionForm
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
@@ -240,5 +272,33 @@ export function TicketExtensionRequestPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <ExtensionRequestDetails request={latestRequest} />;
|
if (isRequestFormOpen && latestRequest.status !== 'pending') {
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setIsRequestFormOpen(false)}
|
||||||
|
>
|
||||||
|
Back to latest request
|
||||||
|
</Button>
|
||||||
|
<RequestExtensionForm
|
||||||
|
ticketId={ticketId}
|
||||||
|
assignmentId={assignmentId}
|
||||||
|
currentDueAt={dueAt}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ExtensionRequestDetails
|
||||||
|
request={latestRequest}
|
||||||
|
requests={extensionRequestQuery.data?.items ?? []}
|
||||||
|
onRequestAnother={
|
||||||
|
canRequest ? () => setIsRequestFormOpen(true) : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user