Skip to content

Commit 67c2c8f

Browse files
committed
fix(SupportCenter): improve ticket view layout
1 parent 6ea1bec commit 67c2c8f

20 files changed

+552
-203
lines changed
+14-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1-
const DefaultLayout = ({ children }: { children: JSX.Element[] | JSX.Element }) => {
2-
return <div className="bg-white min-h-screen">{children}</div>;
1+
const DefaultLayout = ({
2+
children,
3+
altBackground,
4+
}: {
5+
children: JSX.Element[] | JSX.Element;
6+
altBackground?: boolean;
7+
}) => {
8+
return (
9+
<div
10+
className={`${altBackground ? "" : "bg-white"} min-h-screen`}
11+
>
12+
{children}
13+
</div>
14+
);
315
};
416

517
export default DefaultLayout;

client/src/components/support/AssignTicketModal.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ const AssignTicketModal: React.FC<AssignTicketModalProps> = ({
8888
<Modal.Header>Assign Ticket to User(s)</Modal.Header>
8989
<Modal.Content>
9090
<Form onSubmit={(e) => e.preventDefault()}>
91+
<p className="!mt-0">
92+
Assigned users will be notified of new messages and updates on this ticket.
93+
</p>
9194
<Dropdown
9295
id="selectUsers"
9396
options={users.map((u) => ({

client/src/components/support/CreateTicketFlow.tsx

+30-27
Original file line numberDiff line numberDiff line change
@@ -228,68 +228,71 @@ const CreateTicketFlow: React.FC<CreateTicketFlowProps> = ({ isLoggedIn }) => {
228228
</div>
229229
)}
230230
<p className="font-semibold">Request Info</p>
231-
<CtlTextInput
232-
control={control}
233-
name="title"
234-
label="Subject"
235-
placeholder="Enter a subject/brief title for your ticket"
236-
rules={required}
237-
required
238-
maxLength={200}
239-
/>
240231
<div className="mt-2">
241232
<label
242233
className="form-field-label form-required"
243-
htmlFor="selectApps"
234+
htmlFor="selectCategory"
244235
>
245-
Application(s)
236+
Category
246237
</label>
247238
<Controller
248-
name="apps"
239+
name="category"
249240
control={control}
250241
render={({ field }) => (
251242
<Dropdown
252-
id="selectApps"
253-
options={apps.map((app) => ({
254-
key: app.id,
255-
value: app.id,
256-
text: app.name,
257-
}))}
243+
id="selectCategory"
244+
options={SupportTicketCategoryOptions}
258245
{...field}
259246
onChange={(e, { value }) => {
260247
field.onChange(value);
261248
}}
262249
fluid
263250
selection
264-
multiple
265251
search
266-
placeholder="Select the application(s) related to your ticket"
252+
placeholder="Select the category of your ticket"
267253
/>
268254
)}
269255
/>
270256
</div>
257+
<div className="mt-2">
258+
<CtlTextInput
259+
control={control}
260+
name="title"
261+
label="Subject"
262+
placeholder="Enter a subject/brief title for your ticket"
263+
rules={required}
264+
required
265+
maxLength={200}
266+
className=""
267+
/>
268+
</div>
271269
<div className="mt-2">
272270
<label
273271
className="form-field-label form-required"
274-
htmlFor="selectCategory"
272+
htmlFor="selectApps"
275273
>
276-
Category
274+
Application/Library (select all that apply)
277275
</label>
278276
<Controller
279-
name="category"
277+
name="apps"
280278
control={control}
281279
render={({ field }) => (
282280
<Dropdown
283-
id="selectCategory"
284-
options={SupportTicketCategoryOptions}
281+
id="selectApps"
282+
options={apps.map((app) => ({
283+
key: app.id,
284+
value: app.id,
285+
text: app.name,
286+
}))}
285287
{...field}
286288
onChange={(e, { value }) => {
287289
field.onChange(value);
288290
}}
289291
fluid
290292
selection
293+
multiple
291294
search
292-
placeholder="Select the category of your ticket"
295+
placeholder="Select the applications and/or libraries related to your ticket"
293296
/>
294297
)}
295298
/>
@@ -328,7 +331,7 @@ const CreateTicketFlow: React.FC<CreateTicketFlowProps> = ({ isLoggedIn }) => {
328331
control={control}
329332
name="capturedURL"
330333
label="URL (if applicable)"
331-
placeholder="Enter the URL of the page you're having trouble with"
334+
placeholder="Enter the URL of the page this ticket is related to - this may help us resolve your issue faster"
332335
type="url"
333336
/>
334337
</div>

client/src/components/support/Navbar.tsx

+22-11
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,28 @@ const SupportCenterNavbar: React.FC<{}> = () => {
114114
Dashboard
115115
</Button>
116116
) : (
117-
<></>
118-
// <Button
119-
// className="h-10 !w-44"
120-
// color="blue"
121-
// as={Link}
122-
// to="/support/contact"
123-
// size="small"
124-
// >
125-
// <Icon name="text telephone" />
126-
// Contact Support
127-
// </Button>
117+
<>
118+
<Button
119+
className="h-10 !w-44"
120+
color="blue"
121+
as={Link}
122+
to="/support/dashboard"
123+
size="small"
124+
>
125+
<Icon name="ticket" />
126+
My Tickets
127+
</Button>
128+
<Button
129+
className="h-10 !w-44"
130+
color="blue"
131+
as={Link}
132+
to="/support/contact"
133+
size="small"
134+
>
135+
<Icon name="text telephone" />
136+
Contact Support
137+
</Button>
138+
</>
128139
)}
129140
</div>
130141
</div>

client/src/components/support/StaffDashboard.tsx

+36-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PaginationWithItemsSelect } from "../util/PaginationWithItemsSelect";
1010
import { useTypedSelector } from "../../state/hooks";
1111
import { useQuery, useQueryClient } from "@tanstack/react-query";
1212
import LoadingSpinner from "../LoadingSpinner";
13+
const AssignTicketModal = lazy(() => import("./AssignTicketModal"));
1314
const SupportCenterSettingsModal = lazy(
1415
() => import("./SupportCenterSettingsModal")
1516
);
@@ -26,6 +27,8 @@ const StaffDashboard = () => {
2627
const [metricOpen, setMetricOpen] = useState<number>(0);
2728
const [metricAvgMins, setMetricAvgMins] = useState<number>(0);
2829
const [metricWeek, setMetricWeek] = useState<number>(0);
30+
const [showAssignModal, setShowAssignModal] = useState<boolean>(false);
31+
const [selectedTicketId, setSelectedTicketId] = useState<string>("");
2932

3033
const queryClient = useQueryClient();
3134

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

100+
function openAssignModal(ticketId: string) {
101+
setSelectedTicketId(ticketId);
102+
setShowAssignModal(true);
103+
}
104+
105+
function onCloseAssignModal() {
106+
setShowAssignModal(false);
107+
setSelectedTicketId("");
108+
queryClient.invalidateQueries(["openTickets"]);
109+
}
110+
97111
const DashboardMetric = ({
98112
metric,
99113
title,
@@ -124,7 +138,10 @@ const StaffDashboard = () => {
124138
)}
125139
</div>
126140
<div className="flex flex-row justify-between w-full mt-6">
127-
<DashboardMetric metric={metricOpen.toString()} title="Open Tickets" />
141+
<DashboardMetric
142+
metric={metricOpen.toString()}
143+
title="Open/In Progress Tickets"
144+
/>
128145
<DashboardMetric
129146
metric={`${metricAvgMins.toString()} mins`}
130147
title="Average Time to Resolution"
@@ -135,7 +152,7 @@ const StaffDashboard = () => {
135152
/>
136153
</div>
137154
<div className="mt-12">
138-
<p className="text-3xl font-semibold mb-2">Open Tickets</p>
155+
<p className="text-3xl font-semibold mb-2">Open/In Progress Tickets</p>
139156
<PaginationWithItemsSelect
140157
activePage={activePage}
141158
totalPages={totalPages}
@@ -183,6 +200,16 @@ const StaffDashboard = () => {
183200
<Icon name="eye" />
184201
View
185202
</Button>
203+
{ticket.status === "open" && (
204+
<Button
205+
color="green"
206+
size="tiny"
207+
onClick={() => openAssignModal(ticket.uuid)}
208+
>
209+
<Icon name="user plus" />
210+
Assign
211+
</Button>
212+
)}
186213
</Table.Cell>
187214
</Table.Row>
188215
))}
@@ -202,6 +229,13 @@ const StaffDashboard = () => {
202229
open={showSettingsModal}
203230
onClose={() => setShowSettingsModal(false)}
204231
/>
232+
{selectedTicketId && (
233+
<AssignTicketModal
234+
open={showAssignModal}
235+
onClose={onCloseAssignModal}
236+
ticketId={selectedTicketId}
237+
/>
238+
)}
205239
</div>
206240
);
207241
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { Label } from "semantic-ui-react";
2+
import { SupportTicket } from "../../types";
3+
import { format, parseISO } from "date-fns";
4+
5+
interface TicketDetailsProps {
6+
ticket: SupportTicket;
7+
}
8+
9+
const TicketDetails: React.FC<TicketDetailsProps> = ({ ticket }) => {
10+
return (
11+
<div className="flex flex-col border rounded-md p-4 shadow-md bg-white">
12+
<p className="2xl:text-xl">
13+
<span className="font-semibold">Requester:</span>{" "}
14+
{ticket.user && (
15+
<>
16+
<span>
17+
`${ticket.user.firstName} ${ticket.user.lastName} ($
18+
{ticket.user.email})`
19+
</span>
20+
<Label>Authenticated</Label>
21+
</>
22+
)}
23+
{ticket.guest &&
24+
`${ticket.guest.firstName} ${ticket.guest.lastName} (${ticket.guest.email})`}
25+
</p>
26+
<p className="2xl:text-xl">
27+
<span className="font-semibold">Subject:</span> {ticket?.title}
28+
</p>
29+
<p className="2xl:text-xl">
30+
<span className="font-semibold">Date Opened:</span>{" "}
31+
{format(parseISO(ticket.timeOpened), "MM/dd/yyyy hh:mm aa")}
32+
</p>
33+
{ticket.status === "closed" && (
34+
<p className="2xl:text-xl">
35+
<span className="font-semibold">Date Closed:</span>{" "}
36+
{format(parseISO(ticket.timeClosed ?? ""), "MM/dd/yyyy hh:mm aa")}
37+
</p>
38+
)}
39+
<p className="2xl:text-xl">
40+
<span className="font-semibold">Description:</span>{" "}
41+
{ticket?.description}
42+
</p>
43+
</div>
44+
);
45+
};
46+
47+
export default TicketDetails;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useState, useEffect } from "react";
2+
import {
3+
Button,
4+
Comment,
5+
Feed,
6+
FeedContent,
7+
FeedDate,
8+
FeedEvent,
9+
FeedLabel,
10+
FeedLike,
11+
FeedMeta,
12+
FeedSummary,
13+
FeedUser,
14+
Form,
15+
Header,
16+
Icon,
17+
TextArea,
18+
} from "semantic-ui-react";
19+
import {
20+
SupportTicket,
21+
SupportTicketFeedEntry,
22+
SupportTicketMessage,
23+
} from "../../types";
24+
import { format, parseISO } from "date-fns";
25+
import useGlobalError from "../error/ErrorHooks";
26+
import axios from "axios";
27+
import { useForm } from "react-hook-form";
28+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
29+
30+
interface TicketFeedProps {
31+
ticket: SupportTicket;
32+
}
33+
34+
const TicketFeed: React.FC<TicketFeedProps> = ({ ticket }) => {
35+
const { handleGlobalError } = useGlobalError();
36+
37+
const getEntryTimestamp = (entry: SupportTicketFeedEntry) => {
38+
return format(parseISO(entry.date), "MM/dd/yyyy hh:mm aa");
39+
}
40+
41+
const TicketFeedEntry = ({ entry }: { entry: SupportTicketFeedEntry }) => {
42+
return (
43+
<div className="flex flex-row items-center">
44+
<div className="flex flex-row items-center mb-4">
45+
<Icon name="circle" color="blue" />
46+
</div>
47+
<div className="ml-2 mb-2">
48+
<div>
49+
<p>{entry.action}</p>
50+
<p className="text-sm text-slate-500">{entry.blame} - {getEntryTimestamp(entry)}</p>
51+
</div>
52+
</div>
53+
</div>
54+
);
55+
};
56+
57+
return (
58+
<div className="flex flex-col w-full bg-white">
59+
<div className="flex flex-col border shadow-md rounded-md p-4">
60+
<p className="text-2xl font-semibold text-center">Ticket Feed</p>
61+
<div className="flex flex-col mt-8">
62+
{ticket.feed?.length === 0 && (
63+
<p className="text-lg text-center text-gray-500 italic">
64+
No history yet...
65+
</p>
66+
)}
67+
<Feed>
68+
{ticket.feed?.map((f) => (
69+
<TicketFeedEntry entry={f} />
70+
))}
71+
</Feed>
72+
</div>
73+
</div>
74+
</div>
75+
);
76+
};
77+
export default TicketFeed;

0 commit comments

Comments
 (0)