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',
header: 'Ticket / Lot',
header: 'Ticket / Assignment',
size: 170,
cell: ({ row }) => (
<div>
<p className="font-medium">{row.original.ticket_name}</p>
<p className="text-xs text-muted-foreground">
Lot #{row.original.assignment_id}
Assignment #{row.original.assignment_id}
</p>
</div>
),

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
import type { ColumnDef, SortingState } from '@tanstack/react-table';
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 { PERMISSIONS } from '@/constants/permissions';
@@ -58,7 +58,12 @@ export function RoleTable({
},
{
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,
disabled: () => isStatusPending,
onClick: onToggleStatus,

View File

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

View File

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

View File

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

View File

@@ -258,7 +258,7 @@ export function TicketExtensionRequestPanel({
if (!canRequest) {
return (
<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>
);
}

View File

@@ -2,7 +2,7 @@
import type { ReactNode } from 'react';
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 { PERMISSIONS } from '@/constants/permissions';
@@ -59,7 +59,12 @@ export function UserTable({
{
label: (user) =>
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,
disabled: (user) =>
pendingUserId === user.id || user.effective_status === 'pending',

View File

@@ -14,6 +14,14 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Sidebar,
SidebarContent,
@@ -124,6 +132,7 @@ function SidebarNavItem({
item: MenuItem;
pathname: string;
}) {
const { isMobile, state } = useSidebar();
const isActive = isRouteActive(pathname, item.path);
const isChildActive =
item.children?.some((child) => isRouteActive(pathname, child.path)) ??
@@ -136,6 +145,52 @@ function SidebarNavItem({
}, [isChildActive]);
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 (
<Collapsible
asChild

View File

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