Skip to content

Commit bc06126

Browse files
authored
Edit Ticket (#128)
Allow for all aspects of a ticket to be edited via 1 endpoint
1 parent fb965e9 commit bc06126

File tree

5 files changed

+171
-133
lines changed

5 files changed

+171
-133
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { useState } from 'react';
2+
import {
3+
ModalFooter,
4+
Modal,
5+
ModalContent,
6+
ModalHeader,
7+
ModalBody,
8+
ModalCloseButton,
9+
Button,
10+
useColorModeValue,
11+
} from '@chakra-ui/react';
12+
import { TicketWithNames } from '../../server/trpc/router/ticket';
13+
import { DARK_GRAY_COLOR } from '../../utils/constants';
14+
import CreateTicketForm from '../queue/CreateTicketForm';
15+
16+
interface EditTicketModalProps {
17+
isModalOpen: boolean;
18+
setIsModalOpen: (isOpen: boolean) => void;
19+
onSubmit: (_: TicketWithNames) => void;
20+
ticket: TicketWithNames;
21+
}
22+
23+
/** Allows users to modify their ticket description, location, and assignment */
24+
const EditTicketModal = (props: EditTicketModalProps) => {
25+
const { isModalOpen, setIsModalOpen, onSubmit, ticket } = props;
26+
const [existingTicket, setExistingTicket] = useState<TicketWithNames>(ticket);
27+
28+
return (
29+
<Modal size='2xl' isOpen={isModalOpen} onClose={() => setIsModalOpen(false)}>
30+
<ModalContent backgroundColor={useColorModeValue('', DARK_GRAY_COLOR)}>
31+
<ModalHeader>Edit Ticket</ModalHeader>
32+
<ModalCloseButton />
33+
<ModalBody>
34+
<CreateTicketForm
35+
arePublicTicketsEnabled={true}
36+
isEditingTicket={true}
37+
existingTicket={existingTicket}
38+
setExistingTicket={setExistingTicket}
39+
/>
40+
</ModalBody>
41+
<ModalFooter>
42+
<Button variant='ghost' mr={3} onClick={() => setIsModalOpen(false)}>
43+
Cancel
44+
</Button>
45+
<Button colorScheme='blue' onClick={() => onSubmit(existingTicket)}>
46+
Confirm
47+
</Button>
48+
</ModalFooter>
49+
</ModalContent>
50+
</Modal>
51+
);
52+
};
53+
54+
export default EditTicketModal;

src/components/queue/CreateTicketForm.tsx

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useState, useEffect } from 'react';
22
import { trpc } from '../../utils/trpc';
33
import { Select } from 'chakra-react-select';
44
import Router from 'next/router';
@@ -22,6 +22,7 @@ import { PersonalQueue, TicketType } from '@prisma/client';
2222
import { STARTER_CONCEPTUAL_TICKET_DESCRIPTION, STARTER_DEBUGGING_TICKET_DESCRIPTION } from '../../utils/constants';
2323
import { getTicketUrl, uppercaseFirstLetter } from '../../utils/utils';
2424
import ConfirmPublicToggleModal from '../modals/ConfirmPublicToggleModal';
25+
import { TicketWithNames } from '../../server/trpc/router/ticket';
2526

2627
interface Assignment {
2728
id: number;
@@ -38,22 +39,55 @@ interface Location {
3839
interface CreateTicketFormProps {
3940
arePublicTicketsEnabled: boolean;
4041
personalQueue?: PersonalQueue;
42+
isEditingTicket?: boolean;
43+
// Existing ticket is used to prepopulate the form when editing a ticket
44+
existingTicket?: TicketWithNames;
45+
setExistingTicket?: (ticket: TicketWithNames) => void;
4146
}
4247

4348
const CreateTicketForm = (props: CreateTicketFormProps) => {
44-
const { arePublicTicketsEnabled, personalQueue } = props;
45-
const [ticketType, setTicketType] = useState<TicketType>();
46-
const [description, setDescription] = useState<string>('');
47-
const [locationDescription, setLocationDescription] = useState<string>('');
48-
const [assignment, setAssignment] = useState<Assignment>();
49+
const { arePublicTicketsEnabled, personalQueue, isEditingTicket, existingTicket, setExistingTicket } = props;
50+
const [ticketType, setTicketType] = useState<TicketType | undefined>(existingTicket?.ticketType);
51+
const [description, setDescription] = useState<string>(existingTicket?.description ?? '');
52+
const [locationDescription, setLocationDescription] = useState<string>(existingTicket?.locationDescription ?? '');
4953
const [assignmentOptions, setAssignmentOptions] = useState<Assignment[]>([]);
5054
const [locationOptions, setLocationOptions] = useState<Location[]>([]);
5155
const [isPublicModalOpen, setIsPublicModalOpen] = useState<boolean>(false);
52-
const [location, setLocation] = useState<Location>();
53-
const [isPublic, setIsPublic] = useState<boolean>(false);
56+
const [isPublic, setIsPublic] = useState<boolean>(existingTicket?.isPublic ?? false);
5457
const [isButtonLoading, setIsButtonLoading] = useState<boolean>(false);
58+
const [assignment, setAssignment] = useState<Assignment | undefined>(
59+
existingTicket
60+
? { id: existingTicket.assignmentId, label: existingTicket.assignmentName, value: existingTicket.assignmentName }
61+
: undefined,
62+
);
63+
const [location, setLocation] = useState<Location | undefined>(
64+
existingTicket
65+
? {
66+
id: existingTicket.locationId,
67+
label: existingTicket.locationName,
68+
value: existingTicket.locationName,
69+
}
70+
: undefined,
71+
);
5572
const toast = useToast();
5673

74+
// When a property of the ticket changes, update the existing ticket if it exists
75+
useEffect(() => {
76+
if (existingTicket && setExistingTicket) {
77+
setExistingTicket({
78+
...existingTicket,
79+
description,
80+
locationDescription,
81+
assignmentId: assignment?.id ?? existingTicket.assignmentId,
82+
assignmentName: assignment?.label ?? existingTicket.assignmentName,
83+
locationId: location?.id ?? existingTicket.locationId,
84+
locationName: location?.label ?? existingTicket.locationName,
85+
ticketType: ticketType ?? existingTicket.ticketType,
86+
isPublic,
87+
});
88+
}
89+
}, [description, locationDescription, assignment, location, ticketType, isPublic, existingTicket, setExistingTicket]);
90+
5791
const createTicketMutation = trpc.ticket.createTicket.useMutation();
5892
trpc.admin.getActiveAssignments.useQuery(undefined, {
5993
refetchOnWindowFocus: false,
@@ -96,6 +130,11 @@ const CreateTicketForm = (props: CreateTicketFormProps) => {
96130

97131
const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
98132
e.preventDefault();
133+
if (isEditingTicket) {
134+
// Edit ticket has it's own submit handler
135+
return;
136+
}
137+
99138
if (!assignment || !location || !ticketType) {
100139
toast({
101140
title: 'Error',
@@ -232,7 +271,14 @@ const CreateTicketForm = (props: CreateTicketFormProps) => {
232271
</FormLabel>
233272
<Switch isChecked={isPublic} mt={1} onChange={handleTogglePublic} />
234273
</FormControl>
235-
<Button type='submit' width='full' mt={4} colorScheme='whatsapp' isLoading={isButtonLoading}>
274+
<Button
275+
hidden={isEditingTicket}
276+
type='submit'
277+
width='full'
278+
mt={4}
279+
colorScheme='whatsapp'
280+
isLoading={isButtonLoading}
281+
>
236282
Request Help
237283
</Button>
238284
</form>

src/components/queue/TicketQueue.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const TicketQueue = (props: TicketQueueProps) => {
5656
'tickets-marked-as-absent',
5757
'all-tickets-closed',
5858
'tickets-marked-as-priority',
59-
'ticket-description-changed',
59+
'ticket-edited',
6060
'ticket-toggle-public',
6161
];
6262
const shouldInvalidateAssigned = [
@@ -66,7 +66,7 @@ const TicketQueue = (props: TicketQueueProps) => {
6666
'all-tickets-closed',
6767
'ticket-closed',
6868
'tickets-marked-as-priority',
69-
'ticket-description-changed',
69+
'ticket-edited',
7070
'ticket-toggle-public',
7171
];
7272
const shouldInvalidatePending = [
@@ -75,10 +75,15 @@ const TicketQueue = (props: TicketQueueProps) => {
7575
'all-tickets-closed',
7676
'ticket-closed',
7777
'tickets-marked-as-priority',
78-
'ticket-description-changed',
78+
'ticket-edited',
7979
'ticket-toggle-public',
8080
];
81-
const shouldInvalidateAbsent = ['tickets-marked-as-absent', 'ticket-closed'];
81+
const shouldInvalidateAbsent = [
82+
'tickets-marked-as-absent',
83+
'ticket-closed',
84+
'ticket-location-changed',
85+
'ticket-edited',
86+
];
8287

8388
if (message === 'ticket-joined' || message === 'ticket-left') {
8489
context.ticket.getUsersInTicketGroup.invalidate({ ticketId: ticketData.data.id });

0 commit comments

Comments
 (0)