Add more graphql support to Link

This commit is contained in:
Darren Clarke 2023-03-29 14:43:27 +02:00
parent d7f8c87ccb
commit 60f6061d49
14 changed files with 251 additions and 159 deletions

View file

@ -1,6 +1,7 @@
import { FC, useState } from "react";
import { Grid, Button, Dialog, DialogActions, DialogContent, TextField } from "@mui/material";
// import { request, gql } from "graphql-request";
import { useSWRConfig } from "swr";
import { updateTicketMutation } from "graphql/updateTicketMutation";
interface ArticleCreateDialogProps {
ticketID: string;
@ -10,54 +11,25 @@ interface ArticleCreateDialogProps {
}
export const ArticleCreateDialog: FC<ArticleCreateDialogProps> = ({ ticketID, open, closeDialog, kind }) => {
console.log({ ticketID })
const [body, setBody] = useState("");
const backgroundColor = kind === "reply" ? "#1982FC" : "#FFB620";
const color = kind === "reply" ? "white" : "black";
const origin = typeof window !== 'undefined' && window.location.origin
? window.location.origin
: '';
const { fetcher } = useSWRConfig();
const createArticle = async () => {
// const token = document?.querySelector('meta[name="csrf-token"]').getAttribute('content');
// console.log({ token })
const res = await fetch(`${origin}/api/v1/ticket_articles`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": "BG3wYuvTgi4ALfaZ-Mdq6i08wRFRJHeCPJbfGjfVarLRhwaxRC8J-AZvGiSNOiWrN38WT3C9WGLhcmaMb0AqBQ",
},
body: JSON.stringify({
ticket_id: ticketID,
body,
internal: kind === "note",
sender: "Agent",
}),
});
console.log({ res })
/*
const document = gql`
mutation {
ticketUpdate(
await fetcher(
{
document: updateTicketMutation,
variables: {
ticketId: `gid://zammad/Ticket/${ticketID}`,
input: {
ticketId: "1"
body: "This is a test article"
internal: false
}
) {
article {
id
article: {
body,
type: kind === "note" ? "note" : "phone",
internal: kind === "note"
}
}
}
}
`;
const data = await request({
url: `${origin}/graphql`,
document,
});
console.log({ data })
*/
});
closeDialog();
setBody("");
}

View file

@ -51,6 +51,7 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket }) => {
<MessageList style={{ marginBottom: 80 }}>
{ticket.articles.edges.map(({ node: article }: any) => (
<Message
key={article.id}
className={
article.internal
? "internal-note"

View file

@ -1,5 +1,7 @@
import { FC, useEffect, useState } from "react";
import { Grid, Box, Typography, TextField, Stack, Chip, Select, MenuItem } from "@mui/material";
import useSWR, { useSWRConfig } from "swr";
import { updateTicketMutation } from "graphql/updateTicketMutation";
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
interface TicketEditProps {
@ -7,34 +9,58 @@ interface TicketEditProps {
}
export const TicketEdit: FC<TicketEditProps> = ({ ticket }) => {
const [selectedGroup, setSelectedGroup] = useState("group1");
const [selectedOwner, setSelectedOwner] = useState("owner1");
const [selectedPriority, setSelectedPriority] = useState("priority1");
const [selectedState, setSelectedState] = useState("state2");
const [selectedGroup, setSelectedGroup] = useState(1);
const [selectedOwner, setSelectedOwner] = useState(1);
const [selectedPriority, setSelectedPriority] = useState(1);
const [selectedState, setSelectedState] = useState(1);
const [selectedTags, setSelectedTags] = useState(["tag1", "tag2"]);
const handleDelete = () => {
console.info("You clicked the delete icon.");
};
const restFetcher = (url: string) => fetch(url).then((r) => r.json());
const { data: groups } = useSWR("/api/v1/groups", restFetcher);
console.log({ groups });
const { data: users } = useSWR("/api/v1/users", restFetcher);
console.log({ users });
const { data: states } = useSWR("/api/v1/ticket_states", restFetcher);
console.log({ states });
const { data: priorities } = useSWR("/api/v1/ticket_priorities", restFetcher);
console.log({ priorities });
const { fetcher } = useSWRConfig();
const updateTicket = async () => {
await fetcher(
{
document: updateTicketMutation,
variables: {
ticketId: ticket.id,
input: {
ownerId: `gid://zammad/User/${selectedOwner}`,
tags: ["tag1", "tag2"],
}
}
});
}
return (
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
<Grid container direction="column" spacing={3}>
<Grid item>
<Box sx={{ m: 1 }}>Group</Box>
<Select defaultValue={selectedGroup} value={selectedGroup} onChange={(e: any) => setSelectedGroup(e.target.value)} size="small" sx={{
<Select defaultValue={selectedGroup} value={selectedGroup} onChange={(e: any) => { setSelectedGroup(e.target.value); updateTicket() }} size="small" sx={{
width: "100%",
backgroundColor: "white"
}} >
<MenuItem key="group1" value="group1">Default Group</MenuItem>
{groups?.map((group: any) => <MenuItem key={group.id} value={group.id}>{group.name}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Box sx={{ m: 1, mt: 0 }}>Owner</Box>
<Select value={selectedOwner} onChange={(e: any) => setSelectedOwner(e.target.value)} size="small" sx={{
<Select value={selectedOwner} onChange={(e: any) => { setSelectedOwner(e.target.value); updateTicket() }} size="small" sx={{
width: "100%",
backgroundColor: "white",
}} >
<MenuItem value="owner1">Darren Clarke</MenuItem>
<MenuItem value="owner2">Darren Gpcmdln</MenuItem>
{users?.map((user: any) => <MenuItem key={user.id} value={user.id}>{user.firstname} {user.lastname}</MenuItem>)}
</Select>
</Grid>
<Grid item>
@ -43,11 +69,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ ticket }) => {
width: "100%",
backgroundColor: "white"
}} >
<MenuItem value="state1">closed</MenuItem>
<MenuItem value="state2">new</MenuItem>
<MenuItem value="state3">open</MenuItem>
<MenuItem value="state4">pending close</MenuItem>
<MenuItem value="state4">pending reminder</MenuItem>
{states?.map((state: any) => <MenuItem key={state.id} value={state.id}>{state.name}</MenuItem>)}
</Select>
</Grid>
<Grid item>
@ -56,17 +78,14 @@ export const TicketEdit: FC<TicketEditProps> = ({ ticket }) => {
width: "100%",
backgroundColor: "white"
}} >
<MenuItem value="priority1">1 low</MenuItem>
<MenuItem value="priority2">2 normal</MenuItem>
<MenuItem value="priority3">3 high</MenuItem>
{priorities?.map((priority: any) => <MenuItem key={priority.id} value={priority.id}>{priority.name}</MenuItem>)}
</Select>
</Grid>
<Grid item>
<Box sx={{ mb: 1, }}>Tags</Box>
<Stack direction="row" spacing={1} sx={{ backgroundColor: "white", p: 1, borderRadius: "6px", border: "1px solid #bbb", minHeight: 120 }} flexWrap="wrap">
<Chip label="First" onDelete={handleDelete} />
<Chip label="Another" onDelete={handleDelete} />
{selectedTags.map((tag: string) => <Chip key={tag} label={tag} onDelete={handleDelete} />)}
</Stack>
</Grid>
</Grid>

View file

@ -1,4 +1,5 @@
import { FC, useState } from "react";
import { useRouter } from "next/router";
import Iframe from "react-iframe";
type ZammadWrapperProps = {
@ -10,11 +11,12 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
path,
hideSidebar = true,
}) => {
const router = useRouter();
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
const [display, setDisplay] = useState("hidden");
const [display, setDisplay] = useState("none");
const url = `${origin}/zammad${path}`;
console.log({ origin, path, url });
@ -49,7 +51,28 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
"display: none";
}
// @ts-ignore
if (linkElement.contentDocument.querySelector(".overview-header")) {
// @ts-ignore
linkElement.contentDocument.querySelector(".overview-header").style =
"display: none";
}
setDisplay("inherit");
if (linkElement.contentWindow) {
linkElement.contentWindow.addEventListener('hashchange', () => {
const hash = linkElement.contentWindow?.location?.hash ?? ""
if (hash.startsWith("#ticket/zoom/")) {
setDisplay("none");
const ticketID = hash.split("/").pop();
router.push(`/tickets/${ticketID}`);
setTimeout(() => {
setDisplay("inherit");
}, 1000);
}
});
}
}
}}
/>

View file

@ -0,0 +1,40 @@
import { gql } from 'graphql-request';
export const getTicketQuery = gql`
query getTicket($ticketId: ID!) {
ticket(ticket: { ticketId: $ticketId }) {
id
internalId
title
note
number
createdAt
updatedAt
closeAt
tags
state {
id
name
}
owner {
id
email
}
articles {
edges {
node {
id
body
internal
type {
name
}
sender {
name
}
}
}
}
}
}
`;

View file

@ -0,0 +1,10 @@
import { gql } from "graphql-request";
export const updateTicketMutation = gql`
mutation UpdateTicket($ticketId: ID!, $input: TicketUpdateInput!) {
ticketUpdate(ticketId: $ticketId, input: $input) {
ticket {
id
}
}
}`;

View file

@ -1,11 +1,5 @@
module.exports = {
rewrites: async () => ({
beforeFiles: [
{
source: "/#ticket/zoom/:path*",
destination: "/ticket/:path*",
},
],
fallback: [
{
source: "/:path*",

View file

@ -1,4 +1,5 @@
/* eslint-disable react/jsx-props-no-spreading */
import { useState } from "react";
import { AppProps } from "next/app";
import { SessionProvider } from "next-auth/react";
import { CssBaseline } from "@mui/material";
@ -13,6 +14,8 @@ import "@fontsource/roboto/700.css";
import "@fontsource/playfair-display/900.css";
import "styles/global.css";
import { LicenseInfo } from "@mui/x-data-grid-pro";
import { SWRConfig } from "swr";
import { GraphQLClient } from "graphql-request";
LicenseInfo.setLicenseKey(
"fd009c623acc055adb16370731be92e4T1JERVI6NDA3NTQsRVhQSVJZPTE2ODAyNTAwMTUwMDAsS0VZVkVSU0lPTj0x"
@ -27,14 +30,40 @@ interface LinkWebProps extends AppProps {
const LinkWeb = (props: LinkWebProps) => {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
const [csrfToken, setCsrfToken] = useState("");
const origin = typeof window !== 'undefined' && window.location.origin
? window.location.origin : null;
const client = new GraphQLClient(`${origin}/graphql`, {
headers: {
"Content-Type": "application/json",
"Accept": "application/json",
},
});
const graphQLFetcher = async ({ document, variables }: any) => {
const requestHeaders = {
"X-CSRF-Token": csrfToken,
};
const { data, headers } = await client.rawRequest(
document,
variables,
requestHeaders
);
const token = headers.get('CSRF-Token');
setCsrfToken(token);
return data;
};
return (
<SessionProvider session={(pageProps as any).session}>
<CacheProvider value={emotionCache}>
<CssBaseline />
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Component {...pageProps} />
</LocalizationProvider>
<SWRConfig value={{ fetcher: graphQLFetcher }}>
<CssBaseline />
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Component {...pageProps} />
</LocalizationProvider>
</SWRConfig>
</CacheProvider>
</SessionProvider>
);

View file

@ -13,19 +13,11 @@ const withAuthInfo =
return res.redirect("/login");
}
try {
const res2 = await fetch(`http://127.0.0.1:8001`);
console.log({ res2 })
} catch (e) {
console.log({ e })
if (req.headers) {
req.headers['X-Forwarded-User'] = session.email.toLowerCase();
req.headers['host'] = 'zammad.example.com';
}
console.log({ zammad: process.env.ZAMMAD_URL })
req.headers['X-Forwarded-User'] = session.email.toLowerCase();
req.headers['host'] = 'zammad.example.com';
console.log({ headers: req.headers })
return handler(req, res);
};
@ -33,7 +25,7 @@ const proxy = createProxyMiddleware({
target: process.env.ZAMMAD_URL,
changeOrigin: true,
xfwd: true,
ws: true,
ws: false,
pathRewrite: { "^/zammad": "" },
});

View file

@ -1,84 +1,24 @@
import { GetServerSideProps, GetServerSidePropsContext } from "next";
import Head from "next/head";
import useSWR from "swr";
import { request, gql } from "graphql-request";
import { NextPage } from "next";
import { Grid } from "@mui/material";
import { Layout } from "components/Layout";
import { TicketDetail } from "components/TicketDetail";
import { TicketEdit } from "components/TicketEdit";
import { getTicketQuery } from "graphql/getTicketQuery";
type TicketProps = {
id: string;
};
const Ticket: NextPage<TicketProps> = ({ id }) => {
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "";
const graphQLFetcher = async ({ document, variables }: any) => {
const data = await request({
url: `${origin}/graphql`,
document,
variables,
});
console.log({ data });
return data;
};
const { data: ticketData, error: ticketError }: any = useSWR(
{
document: gql`
query getTicket($ticketId: Int!) {
ticket(ticket: { ticketInternalId: $ticketId }) {
id
internalId
title
note
number
createdAt
updatedAt
closeAt
articles {
edges {
node {
id
body
internal
sender {
name
}
}
}
}
}
}
`,
variables: { ticketId: parseInt(id, 10) },
document: getTicketQuery,
variables: { ticketId: `gid://zammad/Ticket/${id}` },
},
graphQLFetcher,
{ refreshInterval: 1000 }
);
const { data: graphqlData2, error: graphqlError2 } = useSWR(
{
document: gql`
{
__schema {
queryType {
name
fields {
name
}
}
}
}
`,
variables: {},
},
graphQLFetcher
{ refreshInterval: 100000 }
);
const shouldRender = !ticketError && ticketData;

View file

@ -22,7 +22,7 @@ const Assigned: FC = () => (
width: "100%",
}}
>
<ZammadWrapper path="/#ticket/view/my_tickets" />
<ZammadWrapper path="/#ticket/view/my_assigned" />
</Grid>
</Grid>
</Layout>