Ticket edit updates
This commit is contained in:
parent
2568547384
commit
87724bb7b8
9 changed files with 297 additions and 352 deletions
|
|
@ -151,7 +151,7 @@ const MenuItem = ({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{badge && badge > 0 ? (
|
{open && badge && badge > 0 ? (
|
||||||
<ListItemSecondaryAction>
|
<ListItemSecondaryAction>
|
||||||
<Typography
|
<Typography
|
||||||
color="textSecondary"
|
color="textSecondary"
|
||||||
|
|
@ -197,7 +197,6 @@ export const Sidebar: FC<SidebarProps> = ({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCounts = async () => {
|
const fetchCounts = async () => {
|
||||||
const counts = await getOverviewTicketCountsAction();
|
const counts = await getOverviewTicketCountsAction();
|
||||||
console.log({ counts });
|
|
||||||
setOverviewCounts(counts);
|
setOverviewCounts(counts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -422,8 +421,9 @@ export const Sidebar: FC<SidebarProps> = ({
|
||||||
/>
|
/>
|
||||||
<Collapse
|
<Collapse
|
||||||
in={
|
in={
|
||||||
pathname.startsWith("/overview") ||
|
open &&
|
||||||
pathname.startsWith("/tickets")
|
(pathname.startsWith("/overview") ||
|
||||||
|
pathname.startsWith("/tickets"))
|
||||||
}
|
}
|
||||||
timeout="auto"
|
timeout="auto"
|
||||||
unmountOnExit
|
unmountOnExit
|
||||||
|
|
@ -512,7 +512,7 @@ export const Sidebar: FC<SidebarProps> = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Collapse
|
<Collapse
|
||||||
in={pathname.startsWith("/leafcutter")}
|
in={open && pathname.startsWith("/leafcutter")}
|
||||||
timeout="auto"
|
timeout="auto"
|
||||||
unmountOnExit
|
unmountOnExit
|
||||||
onClick={undefined}
|
onClick={undefined}
|
||||||
|
|
@ -575,7 +575,7 @@ export const Sidebar: FC<SidebarProps> = ({
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
<Collapse
|
<Collapse
|
||||||
in={pathname.startsWith("/admin/")}
|
in={open && pathname.startsWith("/admin/")}
|
||||||
timeout="auto"
|
timeout="auto"
|
||||||
unmountOnExit
|
unmountOnExit
|
||||||
onClick={undefined}
|
onClick={undefined}
|
||||||
|
|
@ -588,7 +588,7 @@ export const Sidebar: FC<SidebarProps> = ({
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
<Collapse
|
<Collapse
|
||||||
in={pathname.startsWith("/admin/bridge")}
|
in={open && pathname.startsWith("/admin/bridge")}
|
||||||
timeout="auto"
|
timeout="auto"
|
||||||
unmountOnExit
|
unmountOnExit
|
||||||
onClick={undefined}
|
onClick={undefined}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
|
||||||
|
|
||||||
fetchTickets();
|
fetchTickets();
|
||||||
|
|
||||||
const interval = setInterval(fetchTickets, 20000);
|
const interval = setInterval(fetchTickets, 10000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [name]);
|
}, [name]);
|
||||||
|
|
|
||||||
|
|
@ -61,113 +61,115 @@ export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
|
||||||
const shouldRender = !!ticket && !!ticketArticles;
|
const shouldRender = !!ticket && !!ticketArticles;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
shouldRender && (
|
<Box sx={{ height: "100%", width: "100%", background: veryLightGray }}>
|
||||||
<Box sx={{ height: "100%", width: "100%" }}>
|
{shouldRender && (
|
||||||
<MainContainer>
|
<>
|
||||||
<ChatContainer>
|
<MainContainer>
|
||||||
<ConversationHeader>
|
<ChatContainer>
|
||||||
<ConversationHeader.Content>
|
<ConversationHeader>
|
||||||
<Box
|
<ConversationHeader.Content>
|
||||||
sx={{
|
<Box
|
||||||
width: "100%",
|
|
||||||
textAlign: "center",
|
|
||||||
fontWeight: "bold",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="h5"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: poppins.style.fontFamily,
|
width: "100%",
|
||||||
fontWeight: 700,
|
textAlign: "center",
|
||||||
|
fontWeight: "bold",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{ticket.title}
|
<Typography
|
||||||
</Typography>
|
variant="h5"
|
||||||
<Typography
|
sx={{
|
||||||
variant="h6"
|
fontFamily: poppins.style.fontFamily,
|
||||||
sx={{
|
fontWeight: 700,
|
||||||
fontFamily: roboto.style.fontFamily,
|
}}
|
||||||
fontWeight: 400,
|
>
|
||||||
|
{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>
|
</MessageList>
|
||||||
</Box>
|
</ChatContainer>
|
||||||
</ConversationHeader.Content>
|
<Box
|
||||||
</ConversationHeader>
|
sx={{
|
||||||
<MessageList style={{ marginBottom: 80 }}>
|
height: 80,
|
||||||
{ticketArticles.edges.map(({ node: article }: any) => (
|
background: veryLightGray,
|
||||||
<Message
|
borderTop: `1px solid ${lightGray}`,
|
||||||
key={article.id}
|
position: "absolute",
|
||||||
className={
|
bottom: 0,
|
||||||
article.internal
|
width: "100%",
|
||||||
? "internal-note"
|
zIndex: 1000,
|
||||||
: 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 }}
|
|
||||||
>
|
>
|
||||||
<Grid item>
|
<Grid
|
||||||
<Button
|
container
|
||||||
text="Write note to agent"
|
spacing={6}
|
||||||
color="#FFB620"
|
justifyContent="center"
|
||||||
onClick={() => {
|
alignItems="center"
|
||||||
setArticleKind("note");
|
alignContent="center"
|
||||||
setDialogOpen(true);
|
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>
|
||||||
<Grid item>
|
</Box>
|
||||||
<Button
|
</MainContainer>
|
||||||
text="Reply to ticket"
|
<ArticleCreateDialog
|
||||||
kind="primary"
|
ticketID={ticket.internalId}
|
||||||
onClick={() => {
|
open={dialogOpen}
|
||||||
setArticleKind(firstArticleKind);
|
closeDialog={closeDialog}
|
||||||
setDialogOpen(true);
|
kind={articleKind}
|
||||||
}}
|
recipient={recipient}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</>
|
||||||
</Grid>
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</MainContainer>
|
|
||||||
<ArticleCreateDialog
|
|
||||||
ticketID={ticket.internalId}
|
|
||||||
open={dialogOpen}
|
|
||||||
closeDialog={closeDialog}
|
|
||||||
kind={articleKind}
|
|
||||||
recipient={recipient}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC, useEffect, useState } from "react";
|
import { FC, useEffect, useState } from "react";
|
||||||
import { Grid, Box, MenuItem } from "@mui/material";
|
import { Grid, Box } from "@mui/material";
|
||||||
import { useFormState } from "react-dom";
|
|
||||||
import { Select, Button } from "@link-stack/ui";
|
import { Select, Button } from "@link-stack/ui";
|
||||||
import { MuiChipsInput } from "mui-chips-input";
|
import { MuiChipsInput } from "mui-chips-input";
|
||||||
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
|
||||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||||
import {
|
import {
|
||||||
updateTicketAction,
|
updateTicketAction,
|
||||||
getTicketAction,
|
getTicketAction,
|
||||||
getTicketStatesAction,
|
getTicketStatesAction,
|
||||||
getTicketPrioritiesAction,
|
getTicketPrioritiesAction,
|
||||||
getTicketTagsAction,
|
|
||||||
} from "app/_actions/tickets";
|
} from "app/_actions/tickets";
|
||||||
import { getAgentsAction } from "app/_actions/users";
|
import { getAgentsAction } from "app/_actions/users";
|
||||||
import { getGroupsAction } from "app/_actions/groups";
|
import { getGroupsAction } from "app/_actions/groups";
|
||||||
|
|
@ -23,56 +20,51 @@ interface TicketEditProps {
|
||||||
|
|
||||||
export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||||
const [ticket, setTicket] = useState<any>();
|
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 [ticketStates, setTicketStates] = useState<any>();
|
||||||
const [ticketPriorities, setTicketPriorities] = useState<any>();
|
const [ticketPriorities, setTicketPriorities] = useState<any>();
|
||||||
const [groups, setGroups] = useState<any>();
|
const [groups, setGroups] = useState<any>();
|
||||||
const [tags, setTags] = useState<any>();
|
|
||||||
const [agents, setAgents] = useState<any>();
|
const [agents, setAgents] = useState<any>();
|
||||||
const selectedTags = [];
|
const [pendingVisible, setPendingVisible] = useState(false);
|
||||||
const pendingVisible = false;
|
|
||||||
const pendingDate = new Date();
|
|
||||||
const handleDelete = () => {
|
|
||||||
console.info("You clicked the delete icon.");
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredStates = ticketStates?.filter(
|
const filteredStates =
|
||||||
(state: any) => !["new", "merged", "removed"].includes(state.name),
|
ticketStates?.filter(
|
||||||
);
|
(state: any) => !["new", "merged", "removed"].includes(state.label),
|
||||||
|
) ?? [];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAgents = async () => {
|
const fetchAgents = async () => {
|
||||||
const result = await getAgentsAction();
|
const result = await getAgentsAction();
|
||||||
console.log({ agents: result });
|
|
||||||
setAgents(result);
|
setAgents(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchGroups = async () => {
|
const fetchGroups = async () => {
|
||||||
const result = await getGroupsAction();
|
const result = await getGroupsAction();
|
||||||
console.log({ groups: result });
|
|
||||||
setGroups(result);
|
setGroups(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchTicketStates = async () => {
|
const fetchTicketStates = async () => {
|
||||||
const result = await getTicketStatesAction();
|
const result = await getTicketStatesAction();
|
||||||
console.log({ ticketStates: result });
|
|
||||||
setTicketStates(result);
|
setTicketStates(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchTicketPriorities = async () => {
|
const fetchTicketPriorities = async () => {
|
||||||
const result = await getTicketPrioritiesAction();
|
const result = await getTicketPrioritiesAction();
|
||||||
console.log({ ticketPriorities: result });
|
|
||||||
setTicketPriorities(result);
|
setTicketPriorities(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchTicketTags = async () => {
|
|
||||||
const result = await getTicketTagsAction();
|
|
||||||
console.log({ tags: result });
|
|
||||||
setTags(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchTicketStates();
|
fetchTicketStates();
|
||||||
fetchTicketPriorities();
|
fetchTicketPriorities();
|
||||||
fetchTicketTags();
|
|
||||||
fetchAgents();
|
fetchAgents();
|
||||||
fetchGroups();
|
fetchGroups();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
@ -80,185 +72,134 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTicket = async () => {
|
const fetchTicket = async () => {
|
||||||
const result = await getTicketAction(id);
|
const result = await getTicketAction(id);
|
||||||
console.log({ result });
|
|
||||||
setTicket(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();
|
fetchTicket();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
/*
|
const updateFormState = (name: string, value: any) => {
|
||||||
useEffect(() => {
|
setFormState({
|
||||||
|
values: {
|
||||||
|
...formState.values,
|
||||||
|
[name]: value,
|
||||||
|
},
|
||||||
|
});
|
||||||
const stateName = filteredStates?.find(
|
const stateName = filteredStates?.find(
|
||||||
(state: any) => state.id === selectedState,
|
(state: any) => state.id === formState.values.state,
|
||||||
)?.name;
|
)?.name;
|
||||||
setPendingVisible(stateName?.includes("pending") ?? false);
|
setPendingVisible(stateName?.includes("pending") ?? false);
|
||||||
}, [selectedState]);
|
setHasChanges(true);
|
||||||
*/
|
|
||||||
const updateTicket = async (input: any) => {
|
|
||||||
/*
|
|
||||||
console.log({ input });
|
|
||||||
const res = await fetcher({
|
|
||||||
document: updateTicketMutation,
|
|
||||||
variables: {
|
|
||||||
ticketId: `gid://zammad/Ticket/${id}`,
|
|
||||||
input,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log({ res });
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
const updateTags = async (tags: string[]) => {
|
|
||||||
/*
|
const updateTicket = async () => {
|
||||||
console.log({ tags });
|
await updateTicketAction(id, formState.values);
|
||||||
const res = await fetcher({
|
setHasChanges(false);
|
||||||
document: updateTagsMutation,
|
|
||||||
variables: {
|
|
||||||
objectId: `gid://zammad/Ticket/${id}`,
|
|
||||||
tags,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
console.log({ res });
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
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;
|
const shouldRender = !!ticket;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
shouldRender && (
|
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
|
||||||
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
|
{shouldRender && (
|
||||||
<form action={formAction}>
|
<Grid container direction="column" spacing={3}>
|
||||||
<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>
|
<Grid item>
|
||||||
<Box sx={{ m: 1 }}>Group</Box>
|
<Button
|
||||||
<Select
|
text="Save"
|
||||||
name="group"
|
kind="primary"
|
||||||
label="Group"
|
onClick={updateTicket}
|
||||||
formState={formState}
|
disabled={!hasChanges}
|
||||||
getOptions={() => groups}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</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>
|
</Grid>
|
||||||
</form>
|
</Grid>
|
||||||
</Box>
|
)}
|
||||||
)
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -76,35 +76,59 @@ export const createTicketArticleAction = async (
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateTicketAction = async (
|
export const updateTicketAction = async (
|
||||||
currentState: any,
|
ticketID: string,
|
||||||
formData: FormData,
|
ticketInfo: Record<string, any>,
|
||||||
) => {
|
) => {
|
||||||
/*
|
console.log({ ticketID, ticketInfo });
|
||||||
try {
|
try {
|
||||||
const { id, project } = currentState.values;
|
const input = {};
|
||||||
const updatedTicket = {
|
if (ticketInfo.state) {
|
||||||
title: formData.get("title"),
|
input["stateId"] = ticketInfo.state;
|
||||||
};
|
}
|
||||||
await executeMutation({
|
if (ticketInfo.pendingTime) {
|
||||||
project,
|
input["pendingTime"] = ticketInfo.pendingTime;
|
||||||
mutation: UpdateAssetMutation,
|
}
|
||||||
|
if (ticketInfo.priority) {
|
||||||
|
input["priorityId"] = ticketInfo.priority;
|
||||||
|
}
|
||||||
|
if (ticketInfo.group) {
|
||||||
|
input["groupId"] = ticketInfo.group;
|
||||||
|
}
|
||||||
|
if (ticketInfo.owner) {
|
||||||
|
input["ownerId"] = ticketInfo.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await executeGraphQL({
|
||||||
|
query: updateTicketMutation,
|
||||||
variables: {
|
variables: {
|
||||||
id,
|
ticketId: `gid://zammad/Ticket/${ticketID}`,
|
||||||
input: updatedAsset,
|
input,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath(`/${project}/assets/${id}`);
|
if (ticketInfo.tags?.length > 0) {
|
||||||
|
const tagsResult = await executeGraphQL({
|
||||||
|
query: updateTagsMutation,
|
||||||
|
variables: {
|
||||||
|
objectId: `gid://zammad/Ticket/${ticketID}`,
|
||||||
|
tags: ticketInfo.tags,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log({ tagsResult });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ result });
|
||||||
return {
|
return {
|
||||||
...currentState,
|
result,
|
||||||
values: { ...currentState.values, ...updatedAsset, id, project },
|
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return { success: false, message: e?.message ?? "Unknown error" };
|
console.log({ e });
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: e?.message ?? "Unknown error",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTicketAction = async (id: string) => {
|
export const getTicketAction = async (id: string) => {
|
||||||
|
|
@ -112,7 +136,7 @@ export const getTicketAction = async (id: string) => {
|
||||||
query: getTicketQuery,
|
query: getTicketQuery,
|
||||||
variables: { ticketId: `gid://zammad/Ticket/${id}` },
|
variables: { ticketId: `gid://zammad/Ticket/${id}` },
|
||||||
});
|
});
|
||||||
|
console.log({ td: ticketData.ticket });
|
||||||
return ticketData?.ticket;
|
return ticketData?.ticket;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -125,38 +149,6 @@ export const getTicketArticlesAction = async (id: string) => {
|
||||||
return ticketData?.ticketArticles;
|
return ticketData?.ticketArticles;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateTicketTagsAction = async (
|
|
||||||
currentState: any,
|
|
||||||
formData: FormData,
|
|
||||||
) => {
|
|
||||||
/*
|
|
||||||
try {
|
|
||||||
const { id, project } = currentState.values;
|
|
||||||
const updatedTicket = {
|
|
||||||
title: formData.get("title"),
|
|
||||||
};
|
|
||||||
await executeMutation({
|
|
||||||
project,
|
|
||||||
mutation: UpdateAssetMutation,
|
|
||||||
variables: {
|
|
||||||
id,
|
|
||||||
input: updatedAsset,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
revalidatePath(`/${project}/assets/${id}`);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...currentState,
|
|
||||||
values: { ...currentState.values, ...updatedAsset, id, project },
|
|
||||||
success: true,
|
|
||||||
};
|
|
||||||
} catch (e: any) {
|
|
||||||
return { success: false, message: e?.message ?? "Unknown error" };
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTicketStatesAction = async () => {
|
export const getTicketStatesAction = async () => {
|
||||||
const states = await executeREST({
|
const states = await executeREST({
|
||||||
path: "/api/v1/ticket_states",
|
path: "/api/v1/ticket_states",
|
||||||
|
|
@ -164,15 +156,15 @@ export const getTicketStatesAction = async () => {
|
||||||
|
|
||||||
const formattedStates =
|
const formattedStates =
|
||||||
states?.map((state: any) => ({
|
states?.map((state: any) => ({
|
||||||
value: state.id,
|
value: `gid://zammad/Ticket::State/${state.id}`,
|
||||||
label: state.name,
|
label: state.name,
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
return formattedStates;
|
return formattedStates;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTicketTagsAction = async () => {
|
export const getTagsAction = async () => {
|
||||||
const tags = await executeREST({
|
const { tags } = await executeREST({
|
||||||
path: "/api/v1/tags",
|
path: "/api/v1/tags",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -186,7 +178,7 @@ export const getTicketPrioritiesAction = async () => {
|
||||||
|
|
||||||
const formattedPriorities =
|
const formattedPriorities =
|
||||||
priorities?.map((priority: any) => ({
|
priorities?.map((priority: any) => ({
|
||||||
value: priority.id,
|
value: `gid://zammad/Ticket::Priority/${priority.id}`,
|
||||||
label: priority.name,
|
label: priority.name,
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ export const CSRFProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
const response = await fetch("/api/v1/users/me");
|
const response = await fetch("/api/v1/users/me");
|
||||||
const token = response.headers.get("CSRF-Token");
|
const token = response.headers.get("CSRF-Token");
|
||||||
update({ csrfToken: token });
|
update({ csrfToken: token });
|
||||||
console.log({ token });
|
|
||||||
}
|
}
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const nextConfig = {
|
||||||
"@link-stack/ui",
|
"@link-stack/ui",
|
||||||
"@link-stack/bridge-common",
|
"@link-stack/bridge-common",
|
||||||
"@link-stack/bridge-ui",
|
"@link-stack/bridge-ui",
|
||||||
|
"mui-chips-input"
|
||||||
],
|
],
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
linkURL: process.env.LINK_URL ?? "http://localhost:3000",
|
linkURL: process.env.LINK_URL ?? "http://localhost:3000",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { colors } from "../styles/theme";
|
||||||
|
|
||||||
interface InternalButtonProps {
|
interface InternalButtonProps {
|
||||||
text: string;
|
text: string;
|
||||||
|
disabled: boolean;
|
||||||
color?: string;
|
color?: string;
|
||||||
kind?: "primary" | "secondary" | "destructive";
|
kind?: "primary" | "secondary" | "destructive";
|
||||||
type?: string;
|
type?: string;
|
||||||
|
|
@ -27,12 +28,14 @@ export const InternalButton: FC<InternalButtonProps> = ({
|
||||||
text,
|
text,
|
||||||
color,
|
color,
|
||||||
kind,
|
kind,
|
||||||
|
disabled,
|
||||||
type = "button",
|
type = "button",
|
||||||
onClick,
|
onClick,
|
||||||
}) => (
|
}) => (
|
||||||
<MUIButton
|
<MUIButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disableElevation
|
disableElevation
|
||||||
|
disabled={disabled}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
type={type}
|
type={type}
|
||||||
href=""
|
href=""
|
||||||
|
|
@ -56,6 +59,7 @@ export const InternalButton: FC<InternalButtonProps> = ({
|
||||||
|
|
||||||
interface ButtonProps {
|
interface ButtonProps {
|
||||||
text: string;
|
text: string;
|
||||||
|
disabled?: boolean;
|
||||||
color?: string;
|
color?: string;
|
||||||
kind?: "primary" | "secondary" | "destructive";
|
kind?: "primary" | "secondary" | "destructive";
|
||||||
type?: string;
|
type?: string;
|
||||||
|
|
@ -65,6 +69,7 @@ interface ButtonProps {
|
||||||
|
|
||||||
export const Button: FC<ButtonProps> = ({
|
export const Button: FC<ButtonProps> = ({
|
||||||
text,
|
text,
|
||||||
|
disabled = false,
|
||||||
color,
|
color,
|
||||||
type,
|
type,
|
||||||
kind,
|
kind,
|
||||||
|
|
@ -75,6 +80,7 @@ export const Button: FC<ButtonProps> = ({
|
||||||
<Link href={href} passHref>
|
<Link href={href} passHref>
|
||||||
<InternalButton
|
<InternalButton
|
||||||
text={text}
|
text={text}
|
||||||
|
disabled={disabled}
|
||||||
color={color}
|
color={color}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
type={type}
|
type={type}
|
||||||
|
|
@ -84,6 +90,7 @@ export const Button: FC<ButtonProps> = ({
|
||||||
) : (
|
) : (
|
||||||
<InternalButton
|
<InternalButton
|
||||||
text={text}
|
text={text}
|
||||||
|
disabled={disabled}
|
||||||
color={color}
|
color={color}
|
||||||
kind={kind}
|
kind={kind}
|
||||||
type={type}
|
type={type}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ type TextFieldProps = {
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
lines?: number;
|
lines?: number;
|
||||||
helperText?: string;
|
helperText?: string;
|
||||||
|
updateFormState?: (field: string, value: any) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextField: FC<TextFieldProps> = ({
|
export const TextField: FC<TextFieldProps> = ({
|
||||||
|
|
@ -27,6 +28,7 @@ export const TextField: FC<TextFieldProps> = ({
|
||||||
required = false,
|
required = false,
|
||||||
lines = 1,
|
lines = 1,
|
||||||
helperText,
|
helperText,
|
||||||
|
updateFormState,
|
||||||
}) => {
|
}) => {
|
||||||
const { darkMediumGray, white } = colors;
|
const { darkMediumGray, white } = colors;
|
||||||
const { roboto } = fonts;
|
const { roboto } = fonts;
|
||||||
|
|
@ -42,6 +44,7 @@ export const TextField: FC<TextFieldProps> = ({
|
||||||
rows={lines}
|
rows={lines}
|
||||||
required={required}
|
required={required}
|
||||||
defaultValue={formState.values[name]}
|
defaultValue={formState.values[name]}
|
||||||
|
onChange={(e: any) => updateFormState?.(name, e.target.value)}
|
||||||
error={Boolean(formState.errors[name])}
|
error={Boolean(formState.errors[name])}
|
||||||
helperText={formState.errors[name] ?? helperText}
|
helperText={formState.errors[name] ?? helperText}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue