diff --git a/src/components/frontend/helpdesk/TicketCard.tsx b/src/components/frontend/helpdesk/TicketCard.tsx index ceb3e61..d50f61d 100644 --- a/src/components/frontend/helpdesk/TicketCard.tsx +++ b/src/components/frontend/helpdesk/TicketCard.tsx @@ -10,9 +10,12 @@ import { ItemTitle, } from "@/components/ui/item"; import { TicketBadges, TicketCategoryBadge } from "@/components/frontend/helpdesk/TicketBadges"; +import { TicketVoteButton } from "@/components/frontend/helpdesk/TicketVoteButton"; +import type { TicketVoteSummary } from "@/lib/tickets/voteState"; interface TicketCardProps { readonly ticket: Ticket; + readonly voteSummary: TicketVoteSummary; } type UserRelation = number | User | null | undefined; @@ -39,25 +42,23 @@ function timeAgo(iso: string): string { return `${days}d ago`; } -export function TicketCard({ ticket }: TicketCardProps) { +export function TicketCard({ ticket, voteSummary }: TicketCardProps) { const description = convertLexicalToPlaintext({ data: ticket.description }); const reporterName = userName(ticket.reporter, "Unknown reporter"); const assigneeName = userName(ticket.assignee, "Unassigned"); const hasAssignee = ticket.assignee !== null && ticket.assignee !== undefined; return ( - - + #{ticket.id} - {ticket.title} + + {ticket.title} + {description || "No description provided."} @@ -70,8 +71,13 @@ export function TicketCard({ ticket }: TicketCardProps) { {timeAgo(ticket.updatedAt)} + - ); } diff --git a/src/components/frontend/helpdesk/TicketDetail.tsx b/src/components/frontend/helpdesk/TicketDetail.tsx index d6060d0..f0f55d5 100644 --- a/src/components/frontend/helpdesk/TicketDetail.tsx +++ b/src/components/frontend/helpdesk/TicketDetail.tsx @@ -22,6 +22,9 @@ import { TicketTimeline } from "@/components/frontend/helpdesk/TicketTimeline"; import { assignTicket, replyToTicket, updateTicketStatus } from "@/app/(frontend)/helpdesk/actions"; import { CLOSING_STATUSES, TICKET_STATUSES, type TicketStatus } from "@/lib/tickets/ticketMeta"; import { CalendarClockIcon, HistoryIcon, SendIcon, Settings2Icon } from "lucide-react"; +import { EditTicketDialog } from "@/components/frontend/helpdesk/EditTicketDialog"; +import { TicketVoteButton } from "@/components/frontend/helpdesk/TicketVoteButton"; +import type { TicketVoteSummary } from "@/lib/tickets/voteState"; interface AssigneeOption { readonly id: number; @@ -33,6 +36,7 @@ interface TicketDetailProps { readonly currentUserId: number; readonly isStaff: boolean; readonly assigneeOptions: readonly AssigneeOption[]; + readonly voteSummary: TicketVoteSummary; } type UserRelation = number | User | null | undefined; @@ -77,6 +81,7 @@ export function TicketDetail({ currentUserId, isStaff, assigneeOptions, + voteSummary, }: TicketDetailProps) { const router = useRouter(); const [status, setStatus] = useState(ticket.status); @@ -105,6 +110,7 @@ export function TicketDetail({ const canCancel = relationId(ticket.reporter) === currentUserId && !CLOSING_STATUSES.includes(ticket.status); const descriptionText = convertLexicalToPlaintext({ data: ticket.description }); + const isAuthor = relationId(ticket.reporter) === currentUserId; async function applyControl(kind: ControlKind) { setControlBusy(kind); @@ -183,6 +189,15 @@ export function TicketDetail({ +
+ + {isAuthor && } +
diff --git a/src/components/frontend/helpdesk/TicketsView.tsx b/src/components/frontend/helpdesk/TicketsView.tsx index 9572937..6b495aa 100644 --- a/src/components/frontend/helpdesk/TicketsView.tsx +++ b/src/components/frontend/helpdesk/TicketsView.tsx @@ -13,12 +13,13 @@ import { CLOSING_STATUSES, STATUS_STYLES, type TicketStatus } from "@/lib/ticket import { InboxIcon, PlusIcon, SearchIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { usePageCommands } from "@/components/command-palette/CommandPaletteContext"; +import type { TicketVoteSummary } from "@/lib/tickets/voteState"; type TicketFilter = "all" | "open" | "resolved" | "closed" | "unassigned"; interface TicketsViewProps { readonly tickets: readonly Ticket[]; - readonly currentUserId: number; + readonly voteSummaries: readonly TicketVoteSummary[]; readonly isStaff: boolean; } @@ -54,10 +55,14 @@ function isUnassignedTicket(ticket: Ticket): boolean { ); } -export function TicketsView({ tickets, currentUserId: _currentUserId, isStaff }: TicketsViewProps) { +export function TicketsView({ tickets, voteSummaries, isStaff }: TicketsViewProps) { const [activeFilter, setActiveFilter] = useState("all"); const [search, setSearch] = useState(""); const [createOpen, setCreateOpen] = useState(false); + const votesByTicket = useMemo( + () => new Map(voteSummaries.map((summary) => [summary.ticketId, summary])), + [voteSummaries], + ); usePageCommands([ { @@ -200,7 +205,17 @@ export function TicketsView({ tickets, currentUserId: _currentUserId, isStaff }: ) : (
{filteredTickets.map((ticket) => ( - + ))}
)}