diff --git a/apps/client/components/TicketDetails/index.tsx b/apps/client/components/TicketDetails/index.tsx index 19406b717..b07f60d1c 100644 --- a/apps/client/components/TicketDetails/index.tsx +++ b/apps/client/components/TicketDetails/index.tsx @@ -1,26 +1,50 @@ -//@ts-nocheck +// @ts-nocheck +import { + Command, + CommandGroup, + CommandItem, + CommandList, +} from "@/shadcn/ui/command"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger, +} from "@/shadcn/ui/context-menu"; +import { BlockNoteEditor, PartialBlock } from "@blocknote/core"; +import { BlockNoteView } from "@blocknote/mantine"; import { Switch } from "@headlessui/react"; import { CheckCircleIcon } from "@heroicons/react/20/solid"; -import moment from "moment"; -import { useRouter } from "next/router"; -import { useEffect, useRef, useState, useMemo } from "react"; -import { useQuery } from "react-query"; import { Text, Tooltip } from "@radix-ui/themes"; import { getCookie } from "cookies-next"; +import moment from "moment"; import useTranslation from "next-translate/useTranslation"; +import { useRouter } from "next/router"; +import { useEffect, useMemo, useRef, useState } from "react"; import Frame from "react-frame-component"; +import { useQuery } from "react-query"; import { useDebounce } from "use-debounce"; -import { BlockNoteEditor, PartialBlock } from "@blocknote/core"; -import { BlockNoteView } from "@blocknote/mantine"; -import { useUser } from "../../store/session"; -import { IconCombo, UserCombo } from "../Combo"; +import { toast } from "@/shadcn/hooks/use-toast"; +import { cn } from "@/shadcn/lib/utils"; import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/shadcn/ui/dropdown-menu"; +import { + CheckIcon, CircleCheck, CircleDotDashed, Ellipsis, Eye, - EyeClosed, EyeOff, LifeBuoy, Loader, @@ -29,20 +53,11 @@ import { SignalHigh, SignalLow, SignalMedium, - Trash, Trash2, Unlock, } from "lucide-react"; -import { toast } from "@/shadcn/hooks/use-toast"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "@/shadcn/ui/dropdown-menu"; -import { IconKeyboardHide } from "@tabler/icons-react"; +import { useUser } from "../../store/session"; +import { IconCombo, UserCombo } from "../Combo"; const ticketStatusMap = [ { id: 1, value: "needs_support", name: "Needs Support", icon: LifeBuoy }, @@ -396,6 +411,7 @@ export default function Ticket() { // return undefined; // } try { + // @ts-ignore return JSON.parse(storageString) as PartialBlock[]; } catch (e) { return undefined; @@ -441,6 +457,97 @@ export default function Ticket() { setIssue(editor.document); }; + async function updateTicketStatus(e: any, ticket: any) { + await fetch(`/api/v1/ticket/status/update`, { + method: "PUT", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ id: ticket.id, status: !ticket.isComplete }), + }) + .then((res) => res.json()) + .then(() => { + toast({ + title: ticket.isComplete ? "Issue re-opened" : "Issue closed", + description: "The status of the issue has been updated.", + duration: 3000, + }); + refetch(); + }); + } + + // Add these new functions + async function updateTicketAssignee(ticketId: string, user: any) { + try { + const response = await fetch(`/api/v1/ticket/transfer`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + user: user ? user.id : undefined, + id: ticketId, + }), + }); + + if (!response.ok) throw new Error("Failed to update assignee"); + + toast({ + title: "Assignee updated", + description: `Transferred issue successfully`, + duration: 3000, + }); + refetch(); + } catch (error) { + toast({ + title: "Error", + description: "Failed to update assignee", + variant: "destructive", + duration: 3000, + }); + } + } + + async function updateTicketPriority(ticket: any, priority: string) { + try { + const response = await fetch(`/api/v1/ticket/update`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + id: ticket.id, + detail: ticket.detail, + note: ticket.note, + title: ticket.title, + priority: priority, + status: ticket.status, + }), + }).then((res) => res.json()); + + if (!response.success) throw new Error("Failed to update priority"); + + toast({ + title: "Priority updated", + description: `Ticket priority set to ${priority}`, + duration: 3000, + }); + refetch(); + } catch (error) { + toast({ + title: "Error", + description: "Failed to update priority", + variant: "destructive", + duration: 3000, + }); + } + } + + const priorities = ["low", "medium", "high"]; + return (
{status === "loading" && ( @@ -457,482 +564,501 @@ export default function Ticket() { )} {status === "success" && ( -
-
-
-
-
-
-
-

- #{data.ticket.Number} - -

- setTitle(e.target.value)} - key={data.ticket.id} - disabled={data.ticket.locked} - /> -
-
-
- {data.ticket.client && ( -
- - {data.ticket.client.name} - -
- )} -
- {!data.ticket.isComplete ? ( -
- - {t("open_issue")} - + + +
+
+
+
+
+
+
+

+ #{data.ticket.Number} - +

+ setTitle(e.target.value)} + key={data.ticket.id} + disabled={data.ticket.locked} + /> +
+
+
+ {data.ticket.client && ( +
+ + {data.ticket.client.name} + +
+ )} +
+ {!data.ticket.isComplete ? ( +
+ + {t("open_issue")} + +
+ ) : ( +
+ + {t("closed_issue")} + +
+ )}
- ) : ( -
- - {t("closed_issue")} +
+ + {data.ticket.type}
+ {data.ticket.hidden && ( +
+ + Hidden + +
+ )} + {data.ticket.locked && ( +
+ + Locked + +
+ )} +
+ {user.isAdmin && ( + + + + + + + Issue Actions + + + {data.ticket.hidden ? ( + hide(false)} + > + + Show Issue + + ) : ( + hide(true)} + > + + Hide Issue + + )} + {data.ticket.locked ? ( + lock(false)} + > + + Unlock Issue + + ) : ( + lock(true)} + > + + Lock Issue + + )} + + deleteIssue()} + > + + Delete Issue + + + )}
-
- - {data.ticket.type} - -
- {data.ticket.hidden && ( -
- - Hidden - -
- )} - {data.ticket.locked && ( -
- - Locked - -
- )}
- {user.isAdmin && ( - - - - - - - Issue Actions - - - {data.ticket.hidden ? ( - hide(false)} - > - - Show Issue - - ) : ( - hide(true)} - > - - Hide Issue - - )} - {data.ticket.locked ? ( - lock(false)} - > - - Unlock Issue - - ) : ( - lock(true)} - > - - Lock Issue - - )} - - deleteIssue()} - > - - Delete Issue - - - - )}
-
-
- -
-
- {data.ticket.fromImap ? ( - <> - {data.ticket.email} - created via email at - - {moment(data.ticket.createdAt).format("DD/MM/YYYY")} - - - ) : ( - <> - {data.ticket.createdBy ? ( -
- - Created by - - {data.ticket.createdBy.name} - at{" "} + +
+
+ {data.ticket.fromImap ? ( + <> + + {data.ticket.email} - - {moment(data.ticket.createdAt).format("LLL")} + created via email at + + {moment(data.ticket.createdAt).format( + "DD/MM/YYYY" + )} - {data.ticket.name && ( - - for {data.ticket.name} - - )} - {data.ticket.email && ( - - ( {data.ticket.email} ) - - )} -
+ ) : ( -
- Created at - - - {moment(data.ticket.createdAt).format("LLL")} - - {data.ticket.client && ( + <> + {data.ticket.createdBy ? ( +
- for {data.ticket.client.name} + Created by + + {data.ticket.createdBy.name} + {" "} + at{" "} - )} - + + {moment(data.ticket.createdAt).format("LLL")} + + {data.ticket.name && ( + + for {data.ticket.name} + + )} + {data.ticket.email && ( + + ( {data.ticket.email} ) + + )} +
+ ) : ( +
+ Created at + + + {moment(data.ticket.createdAt).format( + "LLL" + )} + + {data.ticket.client && ( + + for{" "} + {data.ticket.client.name} + + )} + +
+ )} + + )} +
+
+ {!data.ticket.fromImap ? ( + <> + + + ) : ( +
+
+ +
)} - - )} -
-
- {!data.ticket.fromImap ? ( - <> - - - ) : ( -
-
- -
- )} -
-
-
-
-
-
-
- - {t("comments")} -
-
- {/* Activity feed*/} -
-
    - {data.ticket.comments.length > 0 && - data.ticket.comments.map( - (item: any, itemIdx: any) => ( -
  • -
    - {itemIdx !== - data.ticket.comments.length - 1 ? ( -
  • - ) - )} -
-
-
-
-
-
-
- -