Move in progress apps temporarily
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@ node_modules
|
|||
build/**
|
||||
dist/**
|
||||
.next/**
|
||||
docker/zammad/auto_install/**
|
||||
|
|
|
|||
97
apps/link/components/ArticleCreateDialog.tsx
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
import { FC, useState } from "react";
|
||||
import { Grid, Button, Dialog, DialogActions, DialogContent, TextField } from "@mui/material";
|
||||
// import { request, gql } from "graphql-request";
|
||||
|
||||
interface ArticleCreateDialogProps {
|
||||
ticketID: string;
|
||||
open: boolean;
|
||||
closeDialog: () => void;
|
||||
kind: "reply" | "note";
|
||||
}
|
||||
|
||||
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 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(
|
||||
input: {
|
||||
ticketId: "1"
|
||||
body: "This is a test article"
|
||||
internal: false
|
||||
}
|
||||
) {
|
||||
article {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
const data = await request({
|
||||
url: `${origin}/graphql`,
|
||||
document,
|
||||
});
|
||||
|
||||
console.log({ data })
|
||||
*/
|
||||
closeDialog();
|
||||
setBody("");
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} maxWidth="sm" fullWidth >
|
||||
<DialogContent>
|
||||
<TextField label={kind === "reply" ? "Write reply" : "Write internal note"} multiline rows={10} fullWidth value={body} onChange={(e: any) => setBody(e.target.value)} />
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pt: 0, pb: 3 }}>
|
||||
<Grid container justifyContent="space-between">
|
||||
<Grid item>
|
||||
<Button sx={{
|
||||
backgroundColor: "white", color: "#666", fontFamily: "Poppins, sans-serif",
|
||||
fontWeight: 700,
|
||||
borderRadius: 2,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => { setBody(""); closeDialog() }}>Cancel</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button sx={{
|
||||
backgroundColor, color, fontFamily: "Poppins, sans-serif",
|
||||
fontWeight: 700,
|
||||
borderRadius: 2,
|
||||
textTransform: "none",
|
||||
px: 3
|
||||
}}
|
||||
onClick={createArticle}
|
||||
>{kind === "reply" ? "Send Reply" : "Save Note"}</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DialogActions>
|
||||
</Dialog >
|
||||
|
||||
);
|
||||
};
|
||||
|
|
@ -1,23 +1,25 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { Grid, Box, Typography, Button } from "@mui/material";
|
||||
import { FC, useState } from "react";
|
||||
import { Grid, Box, Typography, Button, Dialog, DialogActions, DialogContent } from "@mui/material";
|
||||
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
|
||||
import {
|
||||
MainContainer,
|
||||
ChatContainer,
|
||||
MessageList,
|
||||
Message,
|
||||
MessageInput,
|
||||
Conversation,
|
||||
ConversationHeader,
|
||||
} from "@chatscope/chat-ui-kit-react";
|
||||
import { ArticleCreateDialog } from "./ArticleCreateDialog";
|
||||
|
||||
interface TicketDetailProps {
|
||||
ticket: any;
|
||||
articles: any[];
|
||||
}
|
||||
|
||||
export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
||||
console.log({ here: "here", ticket });
|
||||
export const TicketDetail: FC<TicketDetailProps> = ({ ticket }) => {
|
||||
console.log({ ticket })
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [articleKind, setArticleKind] = useState<"reply" | "note">("reply");
|
||||
const closeDialog = () => setDialogOpen(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainContainer>
|
||||
|
|
@ -27,7 +29,6 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
|
|
@ -42,18 +43,18 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
variant="h6"
|
||||
sx={{ fontFamily: "Roboto", fontWeight: 400 }}
|
||||
>{`Ticket #${ticket.number} (created ${new Date(
|
||||
ticket.created_at
|
||||
ticket.createdAt
|
||||
).toLocaleDateString()})`}</Typography>
|
||||
</Box>
|
||||
</ConversationHeader.Content>
|
||||
</ConversationHeader>
|
||||
<MessageList style={{ marginBottom: 80 }}>
|
||||
{articles.map((article: any) => (
|
||||
{ticket.articles.edges.map(({ node: article }: any) => (
|
||||
<Message
|
||||
className={
|
||||
article.internal
|
||||
? "internal-note"
|
||||
: article.sender === "Agent"
|
||||
: article.sender.name === "Agent"
|
||||
? "outgoing-message"
|
||||
: "incoming-message"
|
||||
}
|
||||
|
|
@ -63,8 +64,7 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
sender: article.from,
|
||||
direction:
|
||||
article.sender === "Agent" ? "outgoing" : "incoming",
|
||||
position: "last",
|
||||
type: "html",
|
||||
position: "single",
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
|
|
@ -92,8 +92,7 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
spacing={4}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
alignContent={"center"}
|
||||
sx={{ height: 72, pt: 2 }}
|
||||
alignContent="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Button
|
||||
|
|
@ -109,6 +108,11 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
margin: "20px 0px",
|
||||
whiteSpace: "nowrap",
|
||||
py: "10px",
|
||||
mt: 2
|
||||
}}
|
||||
onClick={() => {
|
||||
setArticleKind("reply");
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
Reply to ticket
|
||||
|
|
@ -129,6 +133,11 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
margin: "20px 0px",
|
||||
whiteSpace: "nowrap",
|
||||
py: "10px",
|
||||
mt: 2
|
||||
}}
|
||||
onClick={() => {
|
||||
setArticleKind("note");
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
>
|
||||
Write note to agent
|
||||
|
|
@ -137,6 +146,7 @@ export const TicketDetail: FC<TicketDetailProps> = ({ ticket, articles }) => {
|
|||
</Grid>
|
||||
</Box>
|
||||
</MainContainer>
|
||||
<ArticleCreateDialog ticketID={ticket.internalId} open={dialogOpen} closeDialog={closeDialog} kind={articleKind} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { Grid, Box, Typography, TextField } from "@mui/material";
|
||||
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
|
||||
import {
|
||||
MainContainer,
|
||||
ChatContainer,
|
||||
MessageList,
|
||||
Message,
|
||||
MessageInput,
|
||||
Conversation,
|
||||
ConversationHeader,
|
||||
} from "@chatscope/chat-ui-kit-react";
|
||||
|
||||
interface TicketEditProps {
|
||||
ticket: any;
|
||||
|
|
|
|||
|
|
@ -3,18 +3,13 @@ module.exports = {
|
|||
locales: ["en", "fr"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
/*
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/zammad#ticket/zoom/:path*",
|
||||
destination: "/ticket/:path*",
|
||||
permanent: true,
|
||||
},
|
||||
];
|
||||
},
|
||||
*/
|
||||
rewrites: async () => ({
|
||||
beforeFiles: [
|
||||
{
|
||||
source: "/#ticket/zoom/:path*",
|
||||
destination: "/ticket/:path*",
|
||||
},
|
||||
],
|
||||
fallback: [
|
||||
{
|
||||
source: "/:path*",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"@fontsource/playfair-display": "^4.5.13",
|
||||
"@fontsource/poppins": "^4.5.10",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"graphql-request": "^5.2.0",
|
||||
"@mui/icons-material": "^5",
|
||||
"@mui/lab": "^5.0.0-alpha.118",
|
||||
"@mui/material": "^5",
|
||||
|
|
|
|||
|
|
@ -13,8 +13,16 @@ 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 })
|
||||
}
|
||||
|
||||
console.log({ zammad: process.env.ZAMMAD_URL })
|
||||
req.headers['X-Forwarded-User'] = session.email.toLowerCase();
|
||||
req.headers['Host'] = 'zammad.example.com';
|
||||
req.headers['host'] = 'zammad.example.com';
|
||||
|
||||
console.log({ headers: req.headers })
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
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";
|
||||
|
|
@ -15,22 +16,72 @@ const Ticket: NextPage<TicketProps> = ({ id }) => {
|
|||
const origin = typeof window !== 'undefined' && window.location.origin
|
||||
? window.location.origin
|
||||
: '';
|
||||
const fetcher = async (url: string) => {
|
||||
const res = await fetch(url);
|
||||
return res.json();
|
||||
}
|
||||
const graphQLFetcher = async ({ document, variables }: any) => {
|
||||
const data = await request({
|
||||
url: `${origin}/graphql`,
|
||||
document,
|
||||
variables
|
||||
})
|
||||
console.log({ data })
|
||||
|
||||
const { data: ticketData, error: ticketError } = useSWR(
|
||||
`${origin}/api/v1/tickets/${id}`,
|
||||
fetcher
|
||||
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) }
|
||||
},
|
||||
graphQLFetcher,
|
||||
{ refreshInterval: 1000 }
|
||||
);
|
||||
|
||||
const { data: articlesData, error: articlesError } = useSWR(
|
||||
`${origin}/api/v1/ticket_articles/by_ticket/${id}`,
|
||||
fetcher
|
||||
const { data: graphqlData2, error: graphqlError2 } = useSWR(
|
||||
{
|
||||
document: gql`
|
||||
{
|
||||
|
||||
__schema {
|
||||
queryType {
|
||||
name
|
||||
fields {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}`, variables: {}
|
||||
},
|
||||
graphQLFetcher
|
||||
);
|
||||
|
||||
const shouldRender = !ticketError && !articlesError && ticketData && articlesData && !ticketData.error && !articlesData.error;
|
||||
const shouldRender = !ticketError && ticketData;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
|
|
@ -39,13 +90,13 @@ const Ticket: NextPage<TicketProps> = ({ id }) => {
|
|||
</Head>
|
||||
{shouldRender && (
|
||||
<Grid container spacing={0} sx={{ height: "100vh" }} direction="row">
|
||||
<Grid item sx={{ height: "100vh" }} xs={10}>
|
||||
<Grid item sx={{ height: "100vh" }} xs={12}>
|
||||
|
||||
<TicketDetail ticket={ticketData} articles={articlesData} />
|
||||
</Grid>
|
||||
<Grid item xs={2} sx={{ height: "100vh" }}>
|
||||
<TicketEdit ticket={ticketData} />
|
||||
<TicketDetail ticket={ticketData.ticket} />
|
||||
</Grid>
|
||||
{/*<Grid item xs={0} sx={{ height: "100vh" }}>
|
||||
<TicketEdit ticket={ticketData.ticket} />
|
||||
</Grid>*/}
|
||||
</Grid>)}
|
||||
{ticketError && <div>{ticketError.toString()}</div>}
|
||||
</Layout>
|
||||
|
|
|
|||
|
|
@ -12,16 +12,30 @@ body {
|
|||
font-family: Roboto !important;
|
||||
font-size: 16px !important;
|
||||
padding: 20px !important;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.incoming-message .cs-message__content {
|
||||
color: white !important;
|
||||
background-color: #43CC47 !important;
|
||||
color: black !important;
|
||||
background-color: #ddd !important;
|
||||
border-radius: 14px !important;
|
||||
margin: 12px;
|
||||
font-family: Roboto !important;
|
||||
font-size: 16px !important;
|
||||
padding: 20px !important;
|
||||
|
||||
}
|
||||
|
||||
.outgoing-message {
|
||||
margin-left: calc(100% - 550px) !important;
|
||||
margin-right: 0 !important;
|
||||
|
||||
}
|
||||
|
||||
.internal-note {
|
||||
margin-left: calc(100% - 350px) !important;
|
||||
margin-right: 0 !important;
|
||||
|
||||
}
|
||||
|
||||
.outgoing-message .cs-message__content {
|
||||
|
|
@ -32,6 +46,7 @@ body {
|
|||
font-family: Roboto !important;
|
||||
font-size: 16px !important;
|
||||
padding: 20px !important;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.incoming-message .cs-message__content a {
|
||||
|
|
@ -40,6 +55,7 @@ body {
|
|||
|
||||
.outgoing-message .cs-message__content a {
|
||||
color: white !important;
|
||||
|
||||
}
|
||||
|
||||
.cs-message-input__content-editor-wrapper {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ x-zammad-vars:
|
|||
&common-zammad-variables
|
||||
MEMCACHE_SERVERS: "zammad-memcached:11211"
|
||||
REDIS_URL: "redis://zammad-redis:6379"
|
||||
ENABLE_EXPERIMENTAL_MOBILE_FRONTEND: "true"
|
||||
|
||||
x-metamigo-vars:
|
||||
&common-metamigo-variables
|
||||
|
|
@ -41,7 +42,6 @@ services:
|
|||
container_name: zammad-elasticsearch
|
||||
environment:
|
||||
- discovery.type=single-node
|
||||
- ingest.geoip.downloader.enabled=false
|
||||
build: ./docker/elasticsearch
|
||||
restart: ${RESTART}
|
||||
volumes:
|
||||
|
|
|
|||
|
|
@ -1 +1,6 @@
|
|||
FROM zammad/zammad-docker-compose:5.3.1-53 AS builder
|
||||
COPY auto_install ${ZAMMAD_TMP_DIR}/auto_install
|
||||
RUN sed -i "s/# create install ready file/bundle exec rake zammad:package:migrate/g" contrib/docker/docker-entrypoint.sh
|
||||
|
||||
FROM zammad/zammad-docker-compose:5.3.1-53
|
||||
COPY --from=builder ${ZAMMAD_TMP_DIR} ${ZAMMAD_TMP_DIR}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 557 B After Width: | Height: | Size: 557 B |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 875 B After Width: | Height: | Size: 875 B |
|
Before Width: | Height: | Size: 686 KiB After Width: | Height: | Size: 686 KiB |
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 215 B After Width: | Height: | Size: 215 B |
|
Before Width: | Height: | Size: 632 B After Width: | Height: | Size: 632 B |
|
Before Width: | Height: | Size: 625 B After Width: | Height: | Size: 625 B |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 844 B After Width: | Height: | Size: 844 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 570 B After Width: | Height: | Size: 570 B |
|
Before Width: | Height: | Size: 473 B After Width: | Height: | Size: 473 B |
|
Before Width: | Height: | Size: 447 B After Width: | Height: | Size: 447 B |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |