Skip to content

Commit

Permalink
fix(SupportCenter): improve ticket view layout
Browse files Browse the repository at this point in the history
  • Loading branch information
jakeaturner committed Jan 28, 2024
1 parent 6ea1bec commit 67c2c8f
Show file tree
Hide file tree
Showing 20 changed files with 552 additions and 203 deletions.
16 changes: 14 additions & 2 deletions client/src/components/kb/DefaultLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
const DefaultLayout = ({ children }: { children: JSX.Element[] | JSX.Element }) => {
return <div className="bg-white min-h-screen">{children}</div>;
const DefaultLayout = ({
children,
altBackground,
}: {
children: JSX.Element[] | JSX.Element;
altBackground?: boolean;
}) => {
return (
<div
className={`${altBackground ? "" : "bg-white"} min-h-screen`}
>
{children}
</div>
);
};

export default DefaultLayout;
3 changes: 3 additions & 0 deletions client/src/components/support/AssignTicketModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ const AssignTicketModal: React.FC<AssignTicketModalProps> = ({
<Modal.Header>Assign Ticket to User(s)</Modal.Header>
<Modal.Content>
<Form onSubmit={(e) => e.preventDefault()}>
<p className="!mt-0">
Assigned users will be notified of new messages and updates on this ticket.
</p>
<Dropdown
id="selectUsers"
options={users.map((u) => ({
Expand Down
57 changes: 30 additions & 27 deletions client/src/components/support/CreateTicketFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -228,68 +228,71 @@ const CreateTicketFlow: React.FC<CreateTicketFlowProps> = ({ isLoggedIn }) => {
</div>
)}
<p className="font-semibold">Request Info</p>
<CtlTextInput
control={control}
name="title"
label="Subject"
placeholder="Enter a subject/brief title for your ticket"
rules={required}
required
maxLength={200}
/>
<div className="mt-2">
<label
className="form-field-label form-required"
htmlFor="selectApps"
htmlFor="selectCategory"
>
Application(s)
Category
</label>
<Controller
name="apps"
name="category"
control={control}
render={({ field }) => (
<Dropdown
id="selectApps"
options={apps.map((app) => ({
key: app.id,
value: app.id,
text: app.name,
}))}
id="selectCategory"
options={SupportTicketCategoryOptions}
{...field}
onChange={(e, { value }) => {
field.onChange(value);
}}
fluid
selection
multiple
search
placeholder="Select the application(s) related to your ticket"
placeholder="Select the category of your ticket"
/>
)}
/>
</div>
<div className="mt-2">
<CtlTextInput
control={control}
name="title"
label="Subject"
placeholder="Enter a subject/brief title for your ticket"
rules={required}
required
maxLength={200}
className=""
/>
</div>
<div className="mt-2">
<label
className="form-field-label form-required"
htmlFor="selectCategory"
htmlFor="selectApps"
>
Category
Application/Library (select all that apply)
</label>
<Controller
name="category"
name="apps"
control={control}
render={({ field }) => (
<Dropdown
id="selectCategory"
options={SupportTicketCategoryOptions}
id="selectApps"
options={apps.map((app) => ({
key: app.id,
value: app.id,
text: app.name,
}))}
{...field}
onChange={(e, { value }) => {
field.onChange(value);
}}
fluid
selection
multiple
search
placeholder="Select the category of your ticket"
placeholder="Select the applications and/or libraries related to your ticket"
/>
)}
/>
Expand Down Expand Up @@ -328,7 +331,7 @@ const CreateTicketFlow: React.FC<CreateTicketFlowProps> = ({ isLoggedIn }) => {
control={control}
name="capturedURL"
label="URL (if applicable)"
placeholder="Enter the URL of the page you're having trouble with"
placeholder="Enter the URL of the page this ticket is related to - this may help us resolve your issue faster"
type="url"
/>
</div>
Expand Down
33 changes: 22 additions & 11 deletions client/src/components/support/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,17 +114,28 @@ const SupportCenterNavbar: React.FC<{}> = () => {
Dashboard
</Button>
) : (
<></>
// <Button
// className="h-10 !w-44"
// color="blue"
// as={Link}
// to="/support/contact"
// size="small"
// >
// <Icon name="text telephone" />
// Contact Support
// </Button>
<>
<Button
className="h-10 !w-44"
color="blue"
as={Link}
to="/support/dashboard"
size="small"
>
<Icon name="ticket" />
My Tickets
</Button>
<Button
className="h-10 !w-44"
color="blue"
as={Link}
to="/support/contact"
size="small"
>
<Icon name="text telephone" />
Contact Support
</Button>
</>
)}
</div>
</div>
Expand Down
38 changes: 36 additions & 2 deletions client/src/components/support/StaffDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { PaginationWithItemsSelect } from "../util/PaginationWithItemsSelect";
import { useTypedSelector } from "../../state/hooks";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import LoadingSpinner from "../LoadingSpinner";
const AssignTicketModal = lazy(() => import("./AssignTicketModal"));
const SupportCenterSettingsModal = lazy(
() => import("./SupportCenterSettingsModal")
);
Expand All @@ -26,6 +27,8 @@ const StaffDashboard = () => {
const [metricOpen, setMetricOpen] = useState<number>(0);
const [metricAvgMins, setMetricAvgMins] = useState<number>(0);
const [metricWeek, setMetricWeek] = useState<number>(0);
const [showAssignModal, setShowAssignModal] = useState<boolean>(false);
const [selectedTicketId, setSelectedTicketId] = useState<string>("");

const queryClient = useQueryClient();

Expand Down Expand Up @@ -94,6 +97,17 @@ const StaffDashboard = () => {
window.open(`/support/ticket/${uuid}`, "_blank");
}

function openAssignModal(ticketId: string) {
setSelectedTicketId(ticketId);
setShowAssignModal(true);
}

function onCloseAssignModal() {
setShowAssignModal(false);
setSelectedTicketId("");
queryClient.invalidateQueries(["openTickets"]);
}

const DashboardMetric = ({
metric,
title,
Expand Down Expand Up @@ -124,7 +138,10 @@ const StaffDashboard = () => {
)}
</div>
<div className="flex flex-row justify-between w-full mt-6">
<DashboardMetric metric={metricOpen.toString()} title="Open Tickets" />
<DashboardMetric
metric={metricOpen.toString()}
title="Open/In Progress Tickets"
/>
<DashboardMetric
metric={`${metricAvgMins.toString()} mins`}
title="Average Time to Resolution"
Expand All @@ -135,7 +152,7 @@ const StaffDashboard = () => {
/>
</div>
<div className="mt-12">
<p className="text-3xl font-semibold mb-2">Open Tickets</p>
<p className="text-3xl font-semibold mb-2">Open/In Progress Tickets</p>
<PaginationWithItemsSelect
activePage={activePage}
totalPages={totalPages}
Expand Down Expand Up @@ -183,6 +200,16 @@ const StaffDashboard = () => {
<Icon name="eye" />
View
</Button>
{ticket.status === "open" && (
<Button
color="green"
size="tiny"
onClick={() => openAssignModal(ticket.uuid)}
>
<Icon name="user plus" />
Assign
</Button>
)}
</Table.Cell>
</Table.Row>
))}
Expand All @@ -202,6 +229,13 @@ const StaffDashboard = () => {
open={showSettingsModal}
onClose={() => setShowSettingsModal(false)}
/>
{selectedTicketId && (
<AssignTicketModal
open={showAssignModal}
onClose={onCloseAssignModal}
ticketId={selectedTicketId}
/>
)}
</div>
);
};
Expand Down
47 changes: 47 additions & 0 deletions client/src/components/support/TicketDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Label } from "semantic-ui-react";
import { SupportTicket } from "../../types";
import { format, parseISO } from "date-fns";

interface TicketDetailsProps {
ticket: SupportTicket;
}

const TicketDetails: React.FC<TicketDetailsProps> = ({ ticket }) => {
return (
<div className="flex flex-col border rounded-md p-4 shadow-md bg-white">
<p className="2xl:text-xl">
<span className="font-semibold">Requester:</span>{" "}
{ticket.user && (
<>
<span>
`${ticket.user.firstName} ${ticket.user.lastName} ($
{ticket.user.email})`
</span>
<Label>Authenticated</Label>
</>
)}
{ticket.guest &&
`${ticket.guest.firstName} ${ticket.guest.lastName} (${ticket.guest.email})`}
</p>
<p className="2xl:text-xl">
<span className="font-semibold">Subject:</span> {ticket?.title}
</p>
<p className="2xl:text-xl">
<span className="font-semibold">Date Opened:</span>{" "}
{format(parseISO(ticket.timeOpened), "MM/dd/yyyy hh:mm aa")}
</p>
{ticket.status === "closed" && (
<p className="2xl:text-xl">
<span className="font-semibold">Date Closed:</span>{" "}
{format(parseISO(ticket.timeClosed ?? ""), "MM/dd/yyyy hh:mm aa")}
</p>
)}
<p className="2xl:text-xl">
<span className="font-semibold">Description:</span>{" "}
{ticket?.description}
</p>
</div>
);
};

export default TicketDetails;
77 changes: 77 additions & 0 deletions client/src/components/support/TicketFeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useState, useEffect } from "react";
import {
Button,
Comment,
Feed,
FeedContent,
FeedDate,
FeedEvent,
FeedLabel,
FeedLike,
FeedMeta,
FeedSummary,
FeedUser,
Form,
Header,
Icon,
TextArea,
} from "semantic-ui-react";
import {
SupportTicket,
SupportTicketFeedEntry,
SupportTicketMessage,
} from "../../types";
import { format, parseISO } from "date-fns";
import useGlobalError from "../error/ErrorHooks";
import axios from "axios";
import { useForm } from "react-hook-form";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

interface TicketFeedProps {
ticket: SupportTicket;
}

const TicketFeed: React.FC<TicketFeedProps> = ({ ticket }) => {
const { handleGlobalError } = useGlobalError();

const getEntryTimestamp = (entry: SupportTicketFeedEntry) => {
return format(parseISO(entry.date), "MM/dd/yyyy hh:mm aa");
}

const TicketFeedEntry = ({ entry }: { entry: SupportTicketFeedEntry }) => {
return (
<div className="flex flex-row items-center">
<div className="flex flex-row items-center mb-4">
<Icon name="circle" color="blue" />
</div>
<div className="ml-2 mb-2">
<div>
<p>{entry.action}</p>
<p className="text-sm text-slate-500">{entry.blame} - {getEntryTimestamp(entry)}</p>
</div>
</div>
</div>
);
};

return (
<div className="flex flex-col w-full bg-white">
<div className="flex flex-col border shadow-md rounded-md p-4">
<p className="text-2xl font-semibold text-center">Ticket Feed</p>
<div className="flex flex-col mt-8">
{ticket.feed?.length === 0 && (
<p className="text-lg text-center text-gray-500 italic">
No history yet...
</p>
)}
<Feed>
{ticket.feed?.map((f) => (
<TicketFeedEntry entry={f} />
))}
</Feed>
</div>
</div>
</div>
);
};
export default TicketFeed;
Loading

0 comments on commit 67c2c8f

Please sign in to comment.