2 Commits

11 changed files with 88 additions and 19 deletions

View File

@@ -68,13 +68,13 @@ export function useExtensionRequestColumns(): ColumnDef<TicketExtensionRequest>[
() => [ () => [
{ {
accessorKey: 'ticket_name', accessorKey: 'ticket_name',
header: 'Ticket / Lot', header: 'Ticket / Assignment',
size: 170, size: 170,
cell: ({ row }) => ( cell: ({ row }) => (
<div> <div>
<p className="font-medium">{row.original.ticket_name}</p> <p className="font-medium">{row.original.ticket_name}</p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
Lot #{row.original.assignment_id} Assignment #{row.original.assignment_id}
</p> </p>
</div> </div>
), ),

View File

@@ -90,8 +90,8 @@ export function ExtensionRequestDecisionDialog({
: 'Reject extension request'} : 'Reject extension request'}
</DialogTitle> </DialogTitle>
<DialogDescription> <DialogDescription>
Review {request.ticket_name}, Lot #{request.assignment_id} before Review {request.ticket_name}, Assignment #{request.assignment_id}{' '}
confirming this decision. before confirming this decision.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
@@ -205,8 +205,8 @@ export function ExtensionRequestDecisionDialog({
<AlertDialogTitle>Reject extension request?</AlertDialogTitle> <AlertDialogTitle>Reject extension request?</AlertDialogTitle>
<AlertDialogDescription> <AlertDialogDescription>
Are you sure you want to reject the extension request for{' '} Are you sure you want to reject the extension request for{' '}
{request.ticket_name}, Lot #{request.assignment_id}? This decision {request.ticket_name}, Assignment #{request.assignment_id}? This
cannot be undone. decision cannot be undone.
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>

View File

@@ -95,7 +95,7 @@ export default function ExtensionRequestsPage() {
<main className="relative z-10 space-y-5"> <main className="relative z-10 space-y-5">
<PageHeader <PageHeader
title="Extension Requests" title="Extension Requests"
description="Track deadline extension requests across all ticket lots." description="Track deadline extension requests across all ticket assignments."
icon={CalendarClock} icon={CalendarClock}
/> />

View File

@@ -2,7 +2,7 @@
import type { ColumnDef, SortingState } from '@tanstack/react-table'; import type { ColumnDef, SortingState } from '@tanstack/react-table';
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { Edit, RotateCcw } from 'lucide-react'; import { Edit, ShieldCheck, ShieldX } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { PERMISSIONS } from '@/constants/permissions'; import { PERMISSIONS } from '@/constants/permissions';
@@ -58,7 +58,12 @@ export function RoleTable({
}, },
{ {
label: (role) => (role.effective_status ? 'Deactivate' : 'Activate'), label: (role) => (role.effective_status ? 'Deactivate' : 'Activate'),
icon: <RotateCcw className="size-4" />, icon: (role) =>
role.effective_status ? (
<ShieldX className="size-4 text-destructive" />
) : (
<ShieldCheck className="size-4 text-emerald-600" />
),
permission: PERMISSIONS.ROLE.DELETE, permission: PERMISSIONS.ROLE.DELETE,
disabled: () => isStatusPending, disabled: () => isStatusPending,
onClick: onToggleStatus, onClick: onToggleStatus,

View File

@@ -147,7 +147,7 @@ export function ExtensionRequestHistoryDialog({
<DialogTitle>Extension Request History</DialogTitle> <DialogTitle>Extension Request History</DialogTitle>
<DialogDescription> <DialogDescription>
Review every deadline extension request and manager decision for Review every deadline extension request and manager decision for
this ticket lot. this ticket assignment.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogBody className="space-y-3"> <DialogBody className="space-y-3">

View File

@@ -18,7 +18,7 @@ export function RequestExtensionAction({
<TicketActionCard <TicketActionCard
icon={Clock3} icon={Clock3}
title={canRequest ? 'Request Extension' : 'Extension Request History'} title={canRequest ? 'Request Extension' : 'Extension Request History'}
contextLabel={`Lot ${assignmentId}`} contextLabel={`Assignment ${assignmentId}`}
> >
<TicketExtensionRequestPanel <TicketExtensionRequestPanel
ticketId={ticketId} ticketId={ticketId}

View File

@@ -115,7 +115,7 @@ function ExtensionReviewForm({
<TicketActionCard <TicketActionCard
icon={CalendarClock} icon={CalendarClock}
title="Extension Request Details" title="Extension Request Details"
contextLabel={`Lot ${assignmentId}`} contextLabel={`Assignment ${assignmentId}`}
> >
<div className="space-y-4"> <div className="space-y-4">
<Requester request={request} /> <Requester request={request} />
@@ -271,7 +271,7 @@ export function ReviewExtensionRequestAction({
<TicketActionCard <TicketActionCard
icon={Clock3} icon={Clock3}
title="Extension Request" title="Extension Request"
contextLabel={`Lot ${assignmentId}`} contextLabel={`Assignment ${assignmentId}`}
> >
<div className="flex items-center justify-center py-8 text-muted-foreground"> <div className="flex items-center justify-center py-8 text-muted-foreground">
<Loader2 className="mr-2 size-4 animate-spin" /> <Loader2 className="mr-2 size-4 animate-spin" />
@@ -298,7 +298,7 @@ export function ReviewExtensionRequestAction({
<TicketActionCard <TicketActionCard
icon={CalendarClock} icon={CalendarClock}
title="Extension Request" title="Extension Request"
contextLabel={`Lot ${assignmentId}`} contextLabel={`Assignment ${assignmentId}`}
> >
<ExtensionRequestDetails request={latestRequest} requests={requests} /> <ExtensionRequestDetails request={latestRequest} requests={requests} />
</TicketActionCard> </TicketActionCard>

View File

@@ -258,7 +258,7 @@ export function TicketExtensionRequestPanel({
if (!canRequest) { if (!canRequest) {
return ( return (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
No extension request history is available for this ticket lot. No extension request history is available for this ticket assignment.
</p> </p>
); );
} }

View File

@@ -2,7 +2,7 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import type { ColumnDef, SortingState } from '@tanstack/react-table'; import type { ColumnDef, SortingState } from '@tanstack/react-table';
import { Edit, RotateCcw } from 'lucide-react'; import { Edit, UserCheck, UserX } from 'lucide-react';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { PERMISSIONS } from '@/constants/permissions'; import { PERMISSIONS } from '@/constants/permissions';
@@ -59,7 +59,12 @@ export function UserTable({
{ {
label: (user) => label: (user) =>
user.effective_status === 'active' ? 'Deactivate' : 'Activate', user.effective_status === 'active' ? 'Deactivate' : 'Activate',
icon: <RotateCcw className="size-4" />, icon: (user) =>
user.effective_status === 'active' ? (
<UserX className="size-4 text-destructive" />
) : (
<UserCheck className="size-4 text-emerald-600" />
),
permission: PERMISSIONS.USER.DELETE, permission: PERMISSIONS.USER.DELETE,
disabled: (user) => disabled: (user) =>
pendingUserId === user.id || user.effective_status === 'pending', pendingUserId === user.id || user.effective_status === 'pending',

View File

@@ -14,6 +14,14 @@ import {
CollapsibleContent, CollapsibleContent,
CollapsibleTrigger, CollapsibleTrigger,
} from '@/components/ui/collapsible'; } from '@/components/ui/collapsible';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
@@ -124,6 +132,7 @@ function SidebarNavItem({
item: MenuItem; item: MenuItem;
pathname: string; pathname: string;
}) { }) {
const { isMobile, state } = useSidebar();
const isActive = isRouteActive(pathname, item.path); const isActive = isRouteActive(pathname, item.path);
const isChildActive = const isChildActive =
item.children?.some((child) => isRouteActive(pathname, child.path)) ?? item.children?.some((child) => isRouteActive(pathname, child.path)) ??
@@ -136,6 +145,52 @@ function SidebarNavItem({
}, [isChildActive]); }, [isChildActive]);
if (hasChildren) { if (hasChildren) {
if (state === 'collapsed' && !isMobile) {
return (
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
isActive={isChildActive}
aria-label={item.title}
>
<item.icon />
<span>{item.title}</span>
<ChevronRight className="ml-auto" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
side="right"
align="start"
sideOffset={8}
className="min-w-48"
>
<DropdownMenuLabel>{item.title}</DropdownMenuLabel>
<DropdownMenuSeparator />
{item.children?.map((child) =>
child.path ? (
<DropdownMenuItem
key={child.title}
asChild
className={
isRouteActive(pathname, child.path)
? 'bg-accent text-accent-foreground'
: undefined
}
>
<Link href={child.path}>
<child.icon />
<span>{child.title}</span>
</Link>
</DropdownMenuItem>
) : null,
)}
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
);
}
return ( return (
<Collapsible <Collapsible
asChild asChild

View File

@@ -41,7 +41,7 @@ export type ColumnAlign = 'left' | 'right';
export interface DataTableAction<TData> { export interface DataTableAction<TData> {
label: string | ((item: TData) => string); label: string | ((item: TData) => string);
icon: React.ReactNode; icon: React.ReactNode | ((item: TData) => React.ReactNode);
onClick: (item: TData) => void; onClick: (item: TData) => void;
permission?: PermissionInput; permission?: PermissionInput;
disabled?: (item: TData) => boolean; disabled?: (item: TData) => boolean;
@@ -163,6 +163,10 @@ function DataTableContent<TData, TValue>({
typeof action.label === 'function' typeof action.label === 'function'
? action.label(item) ? action.label(item)
: action.label; : action.label;
const icon =
typeof action.icon === 'function'
? action.icon(item)
: action.icon;
return ( return (
<TableActionButton <TableActionButton
@@ -176,7 +180,7 @@ function DataTableContent<TData, TValue>({
action.onClick(item); action.onClick(item);
}} }}
> >
{action.icon} {icon}
</TableActionButton> </TableActionButton>
); );
})} })}