1
0
Fork 0
polaris-task-force/src/components/frontend/dashboard/MyTickets.tsx
Z8MB1E f0af778713 feat(dashboard): customizable dashboard with personal widgets
- Drag-and-drop widget arrangement (main two-column zone + sidebar) with
  per-widget span resize, persisted to Users.preferences.dashboard.layout
  via saveDashboardLayout (migration included)
- New personal widgets: Wallet, Open Negotiations, My Tickets (View all),
  Recent Notifications (per-row mark as read)
- Widgets that are empty or not visible to the viewer are omitted so they
  never render as empty drag targets
- Registers both pending migrations and regenerates payload types/schema
2026-09-10 20:03:27 -04:00

79 lines
2.8 KiB
TypeScript

import Link from "next/link";
import type { Ticket } from "@/payload-types";
import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item";
import { ChevronRightIcon, LifeBuoyIcon } from "lucide-react";
import {
CLOSING_STATUSES,
STATUS_STYLES,
statusLabel,
} from "@/lib/tickets/ticketMeta";
interface MyTicketsProps {
readonly tickets: Ticket[];
readonly openCount: number;
}
function timeAgo(dateStr: string): string {
const now = new Date();
const date = new Date(dateStr);
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / 60000);
if (diffMins < 1) return "just now";
if (diffMins < 60) return `${diffMins}m ago`;
const diffHours = Math.floor(diffMins / 60);
if (diffHours < 24) return `${diffHours}h ago`;
const diffDays = Math.floor(diffHours / 24);
return `${diffDays}d ago`;
}
export function MyTickets({ tickets, openCount }: MyTicketsProps) {
return (
<Item variant="outline">
<ItemHeader>
<div className="flex items-center gap-2">
<LifeBuoyIcon className="size-4 text-muted-foreground" />
<ItemTitle>My Tickets</ItemTitle>
{openCount > 0 && (
<span className="text-xs text-muted-foreground">{openCount} open</span>
)}
</div>
<Link
href="/helpdesk"
className="text-xs text-muted-foreground transition-colors hover:text-primary"
>
View all
</Link>
</ItemHeader>
<ItemContent>
{tickets.length === 0 ? (
<p className="text-sm italic text-muted-foreground">No tickets yet.</p>
) : (
<div className="flex flex-col gap-1">
{tickets.map((ticket) => (
<Link
key={ticket.id}
href={`/helpdesk/${ticket.id}`}
className="group flex items-center gap-3 rounded-md p-2 transition-colors hover:bg-muted/50"
>
<span
className={`shrink-0 rounded border px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider ${STATUS_STYLES[ticket.status]}`}
>
{statusLabel(ticket.status)}
</span>
<div className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="truncate text-sm font-medium transition-colors group-hover:text-primary">
{ticket.title}
</span>
<span className="text-xs text-muted-foreground">
Updated {timeAgo(ticket.updatedAt)}
</span>
</div>
<ChevronRightIcon className="size-3 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" />
</Link>
))}
</div>
)}
</ItemContent>
</Item>
);
}