Ticket edit updates
This commit is contained in:
parent
2568547384
commit
87724bb7b8
9 changed files with 297 additions and 352 deletions
|
|
@ -61,113 +61,115 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
|
|||
const shouldRender = !!ticket && !!ticketArticles;
|
||||
|
||||
return (
|
||||
shouldRender && (
|
||||
<Box sx={{ height: "100%", width: "100%" }}>
|
||||
<MainContainer>
|
||||
<ChatContainer>
|
||||
<ConversationHeader>
|
||||
<ConversationHeader.Content>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
<Box sx={{ height: "100%", width: "100%", background: veryLightGray }}>
|
||||
{shouldRender && (
|
||||
<>
|
||||
<MainContainer>
|
||||
<ChatContainer>
|
||||
<ConversationHeader>
|
||||
<ConversationHeader.Content>
|
||||
<Box
|
||||
sx={{
|
||||
fontFamily: poppins.style.fontFamily,
|
||||
fontWeight: 700,
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
{ticket.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
fontWeight: 400,
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontFamily: poppins.style.fontFamily,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{ticket.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>{`Ticket #${ticket.number} (created ${new Date(
|
||||
ticket.createdAt,
|
||||
).toLocaleDateString()})`}</Typography>
|
||||
</Box>
|
||||
</ConversationHeader.Content>
|
||||
</ConversationHeader>
|
||||
<MessageList style={{ marginBottom: 80 }}>
|
||||
{ticketArticles.edges.map(({ node: article }: any) => (
|
||||
<Message
|
||||
key={article.id}
|
||||
className={
|
||||
article.internal
|
||||
? "internal-note"
|
||||
: article?.sender?.name === "Agent"
|
||||
? "outgoing-message"
|
||||
: "incoming-message"
|
||||
}
|
||||
model={{
|
||||
message: article.bodyWithUrls,
|
||||
sentTime: article.updated_at,
|
||||
sender: article.from,
|
||||
direction:
|
||||
article.sender === "Agent" ? "outgoing" : "incoming",
|
||||
position: "single",
|
||||
}}
|
||||
>{`Ticket #${ticket.number} (created ${new Date(
|
||||
ticket.createdAt,
|
||||
).toLocaleDateString()})`}</Typography>
|
||||
</Box>
|
||||
</ConversationHeader.Content>
|
||||
</ConversationHeader>
|
||||
<MessageList style={{ marginBottom: 80 }}>
|
||||
{ticketArticles.edges.map(({ node: article }: any) => (
|
||||
<Message
|
||||
key={article.id}
|
||||
className={
|
||||
article.internal
|
||||
? "internal-note"
|
||||
: article?.sender?.name === "Agent"
|
||||
? "outgoing-message"
|
||||
: "incoming-message"
|
||||
}
|
||||
model={{
|
||||
message: article.bodyWithUrls,
|
||||
sentTime: article.updated_at,
|
||||
sender: article.from,
|
||||
direction:
|
||||
article.sender === "Agent" ? "outgoing" : "incoming",
|
||||
position: "single",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</MessageList>
|
||||
</ChatContainer>
|
||||
<Box
|
||||
sx={{
|
||||
height: 80,
|
||||
background: veryLightGray,
|
||||
borderTop: `1px solid ${lightGray}`,
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
spacing={6}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
sx={{ height: "100%", pt: 6 }}
|
||||
/>
|
||||
))}
|
||||
</MessageList>
|
||||
</ChatContainer>
|
||||
<Box
|
||||
sx={{
|
||||
height: 80,
|
||||
background: veryLightGray,
|
||||
borderTop: `1px solid ${lightGray}`,
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
zIndex: 1000,
|
||||
}}
|
||||
>
|
||||
<Grid item>
|
||||
<Button
|
||||
text="Write note to agent"
|
||||
color="#FFB620"
|
||||
onClick={() => {
|
||||
setArticleKind("note");
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Grid
|
||||
container
|
||||
spacing={6}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
sx={{ height: "100%", pt: 6 }}
|
||||
>
|
||||
<Grid item>
|
||||
<Button
|
||||
text="Write note to agent"
|
||||
color="#FFB620"
|
||||
onClick={() => {
|
||||
setArticleKind("note");
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
text="Reply to ticket"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
setArticleKind(firstArticleKind);
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
text="Reply to ticket"
|
||||
kind="primary"
|
||||
onClick={() => {
|
||||
setArticleKind(firstArticleKind);
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
</MainContainer>
|
||||
<ArticleCreateDialog
|
||||
ticketID={ticket.internalId}
|
||||
open={dialogOpen}
|
||||
closeDialog={closeDialog}
|
||||
kind={articleKind}
|
||||
recipient={recipient}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
</Box>
|
||||
</MainContainer>
|
||||
<ArticleCreateDialog
|
||||
ticketID={ticket.internalId}
|
||||
open={dialogOpen}
|
||||
closeDialog={closeDialog}
|
||||
kind={articleKind}
|
||||
recipient={recipient}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { Grid, Box, MenuItem } from "@mui/material";
|
||||
import { useFormState } from "react-dom";
|
||||
import { Grid, Box } from "@mui/material";
|
||||
import { Select, Button } from "@link-stack/ui";
|
||||
import { MuiChipsInput } from "mui-chips-input";
|
||||
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import {
|
||||
updateTicketAction,
|
||||
getTicketAction,
|
||||
getTicketStatesAction,
|
||||
getTicketPrioritiesAction,
|
||||
getTicketTagsAction,
|
||||
} from "app/_actions/tickets";
|
||||
import { getAgentsAction } from "app/_actions/users";
|
||||
import { getGroupsAction } from "app/_actions/groups";
|
||||
|
|
@ -23,56 +20,51 @@ interface TicketEditProps {
|
|||
|
||||
export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||
const [ticket, setTicket] = useState<any>();
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [formState, setFormState] = useState({
|
||||
values: {
|
||||
group: null,
|
||||
owner: null,
|
||||
priority: null,
|
||||
pendingTime: null,
|
||||
state: null,
|
||||
tags: [],
|
||||
},
|
||||
});
|
||||
const [ticketStates, setTicketStates] = useState<any>();
|
||||
const [ticketPriorities, setTicketPriorities] = useState<any>();
|
||||
const [groups, setGroups] = useState<any>();
|
||||
const [tags, setTags] = useState<any>();
|
||||
const [agents, setAgents] = useState<any>();
|
||||
const selectedTags = [];
|
||||
const pendingVisible = false;
|
||||
const pendingDate = new Date();
|
||||
const handleDelete = () => {
|
||||
console.info("You clicked the delete icon.");
|
||||
};
|
||||
const [pendingVisible, setPendingVisible] = useState(false);
|
||||
|
||||
const filteredStates = ticketStates?.filter(
|
||||
(state: any) => !["new", "merged", "removed"].includes(state.name),
|
||||
);
|
||||
const filteredStates =
|
||||
ticketStates?.filter(
|
||||
(state: any) => !["new", "merged", "removed"].includes(state.label),
|
||||
) ?? [];
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAgents = async () => {
|
||||
const result = await getAgentsAction();
|
||||
console.log({ agents: result });
|
||||
setAgents(result);
|
||||
};
|
||||
|
||||
const fetchGroups = async () => {
|
||||
const result = await getGroupsAction();
|
||||
console.log({ groups: result });
|
||||
setGroups(result);
|
||||
};
|
||||
|
||||
const fetchTicketStates = async () => {
|
||||
const result = await getTicketStatesAction();
|
||||
console.log({ ticketStates: result });
|
||||
setTicketStates(result);
|
||||
};
|
||||
|
||||
const fetchTicketPriorities = async () => {
|
||||
const result = await getTicketPrioritiesAction();
|
||||
console.log({ ticketPriorities: result });
|
||||
setTicketPriorities(result);
|
||||
};
|
||||
|
||||
const fetchTicketTags = async () => {
|
||||
const result = await getTicketTagsAction();
|
||||
console.log({ tags: result });
|
||||
setTags(result);
|
||||
};
|
||||
|
||||
fetchTicketStates();
|
||||
fetchTicketPriorities();
|
||||
fetchTicketTags();
|
||||
fetchAgents();
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
|
@ -80,185 +72,134 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
useEffect(() => {
|
||||
const fetchTicket = async () => {
|
||||
const result = await getTicketAction(id);
|
||||
console.log({ result });
|
||||
setTicket(result);
|
||||
setFormState({
|
||||
values: {
|
||||
...formState.values,
|
||||
group: result?.group?.id,
|
||||
owner: result?.owner?.id,
|
||||
priority: result?.priority?.id,
|
||||
state: result?.state?.id,
|
||||
tags: result?.tags,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
fetchTicket();
|
||||
}, []);
|
||||
|
||||
/*
|
||||
useEffect(() => {
|
||||
|
||||
const updateFormState = (name: string, value: any) => {
|
||||
setFormState({
|
||||
values: {
|
||||
...formState.values,
|
||||
[name]: value,
|
||||
},
|
||||
});
|
||||
const stateName = filteredStates?.find(
|
||||
(state: any) => state.id === selectedState,
|
||||
(state: any) => state.id === formState.values.state,
|
||||
)?.name;
|
||||
setPendingVisible(stateName?.includes("pending") ?? false);
|
||||
}, [selectedState]);
|
||||
*/
|
||||
const updateTicket = async (input: any) => {
|
||||
/*
|
||||
console.log({ input });
|
||||
const res = await fetcher({
|
||||
document: updateTicketMutation,
|
||||
variables: {
|
||||
ticketId: `gid://zammad/Ticket/${id}`,
|
||||
input,
|
||||
},
|
||||
});
|
||||
console.log({ res });
|
||||
*/
|
||||
setHasChanges(true);
|
||||
};
|
||||
const updateTags = async (tags: string[]) => {
|
||||
/*
|
||||
console.log({ tags });
|
||||
const res = await fetcher({
|
||||
document: updateTagsMutation,
|
||||
variables: {
|
||||
objectId: `gid://zammad/Ticket/${id}`,
|
||||
tags,
|
||||
},
|
||||
});
|
||||
console.log({ res });
|
||||
*/
|
||||
|
||||
const updateTicket = async () => {
|
||||
await updateTicketAction(id, formState.values);
|
||||
setHasChanges(false);
|
||||
};
|
||||
const initialState = {
|
||||
messages: [],
|
||||
errors: [],
|
||||
values: {
|
||||
customer: "",
|
||||
group: ticket?.group?.id?.split("/").pop(),
|
||||
owner: ticket?.owner?.id?.split("/").pop(),
|
||||
priority: ticket?.priority?.id?.split("/").pop(),
|
||||
state: ticket?.state?.id?.split("/").pop(),
|
||||
tags: [],
|
||||
title: "",
|
||||
article: {
|
||||
body: "",
|
||||
type: "note",
|
||||
internal: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const [formState, formAction] = useFormState(
|
||||
updateTicketAction,
|
||||
initialState,
|
||||
);
|
||||
|
||||
const shouldRender = !!ticket;
|
||||
|
||||
return (
|
||||
shouldRender && (
|
||||
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
|
||||
<form action={formAction}>
|
||||
<Grid container direction="column" spacing={3}>
|
||||
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
|
||||
{shouldRender && (
|
||||
<Grid container direction="column" spacing={3}>
|
||||
<Grid item>
|
||||
<Box sx={{ m: 1 }}>Group</Box>
|
||||
<Select
|
||||
name="group"
|
||||
label="Group"
|
||||
formState={formState}
|
||||
updateFormState={updateFormState}
|
||||
getOptions={() => groups}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
|
||||
<Select
|
||||
name="owner"
|
||||
label="Owner"
|
||||
formState={formState}
|
||||
updateFormState={updateFormState}
|
||||
getOptions={() => agents}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ m: 1, mt: 0 }}>State</Box>
|
||||
<Select
|
||||
name="state"
|
||||
label="State"
|
||||
formState={formState}
|
||||
updateFormState={updateFormState}
|
||||
getOptions={() => filteredStates}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sx={{ display: pendingVisible ? "inherit" : "none" }}
|
||||
>
|
||||
<DatePicker
|
||||
label="Pending Date"
|
||||
value={new Date(formState.values.pendingTime)}
|
||||
onChange={(newValue: any) => {
|
||||
updateFormState("pendingDate", newValue.toISOString());
|
||||
}}
|
||||
slotProps={{ textField: { size: "small" } }}
|
||||
sx={{
|
||||
width: "100%",
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box sx={{ m: 1, mt: 0 }}>Priority</Box>
|
||||
<Select
|
||||
name="priority"
|
||||
label="Priority"
|
||||
formState={formState}
|
||||
updateFormState={updateFormState}
|
||||
getOptions={() => ticketPriorities}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box sx={{ mb: 1 }}>Tags</Box>
|
||||
<MuiChipsInput
|
||||
sx={{ backgroundColor: "white", width: "100%" }}
|
||||
value={formState.values.tags}
|
||||
hideClearAll
|
||||
onChange={(tags: any) => {
|
||||
updateFormState("tags", tags);
|
||||
}}
|
||||
onDeleteChip={(tag: any) => {
|
||||
const tags = formState.values.tags.filter(
|
||||
(t: any) => t !== tag,
|
||||
);
|
||||
updateFormState("tags", tags);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item container direction="row-reverse">
|
||||
<Grid item>
|
||||
<Box sx={{ m: 1 }}>Group</Box>
|
||||
<Select
|
||||
name="group"
|
||||
label="Group"
|
||||
formState={formState}
|
||||
getOptions={() => groups}
|
||||
<Button
|
||||
text="Save"
|
||||
kind="primary"
|
||||
onClick={updateTicket}
|
||||
disabled={!hasChanges}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
|
||||
<Select
|
||||
name="owner"
|
||||
label="Owner"
|
||||
formState={formState}
|
||||
getOptions={() => agents}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Box sx={{ m: 1, mt: 0 }}>State</Box>
|
||||
<Select
|
||||
name="state"
|
||||
label="State"
|
||||
formState={formState}
|
||||
getOptions={() =>
|
||||
filteredStates?.map((state: any) => ({
|
||||
value: state.id,
|
||||
label: state.name,
|
||||
})) ?? []
|
||||
}
|
||||
/*
|
||||
onChange={(e: any) => {
|
||||
const newState = e.target.value;
|
||||
setSelectedState(newState);
|
||||
updateTicket({
|
||||
stateId: `gid://zammad/Ticket::State/${newState}`,
|
||||
pendingTime: pendingDate.toISOString(),
|
||||
});
|
||||
}}
|
||||
*/
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sx={{ display: pendingVisible ? "inherit" : "none" }}
|
||||
>
|
||||
{/* <DatePicker
|
||||
label="Pending Date"
|
||||
value={pendingDate}
|
||||
onChange={(newValue: any) => {
|
||||
|
||||
console.log(newValue);
|
||||
setPendingDate(newValue);
|
||||
updateTicket({
|
||||
pendingTime: newValue.toISOString(),
|
||||
})
|
||||
|
||||
}}
|
||||
slotProps={{ textField: { size: "small" } }}
|
||||
sx={{
|
||||
width: "100%",
|
||||
backgroundColor: "white",
|
||||
}}
|
||||
/> */}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box sx={{ m: 1, mt: 0 }}>Priority</Box>
|
||||
<Select
|
||||
name="priority"
|
||||
label="Priority"
|
||||
formState={formState}
|
||||
getOptions={() => ticketPriorities}
|
||||
/*
|
||||
onChange={(e: any) => {
|
||||
|
||||
const newPriority = e.target.value;
|
||||
setSelectedPriority(newPriority);
|
||||
updateTicket({
|
||||
priorityId: `gid://zammad/Ticket::Priority/${newPriority}`,
|
||||
});
|
||||
|
||||
}}
|
||||
*/
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<Box sx={{ mb: 1 }}>Tags</Box>
|
||||
<MuiChipsInput
|
||||
sx={{ backgroundColor: "white", width: "100%" }}
|
||||
value={selectedTags}
|
||||
onChange={(tags: any) => {
|
||||
/*
|
||||
setSelectedTags(tags);
|
||||
updateTags(tags);
|
||||
*/
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item container direction="row-reverse">
|
||||
<Grid item>
|
||||
<Button text="Save" kind="primary" type="submit" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</Box>
|
||||
)
|
||||
</Grid>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue