From dce765033d3b674a55517c8f30edd5e138dd8ab3 Mon Sep 17 00:00:00 2001 From: Darren Clarke Date: Wed, 7 Jun 2023 08:02:29 +0000 Subject: [PATCH] Ticket list updates --- apps/link/components/Sidebar.tsx | 68 ++++++++++----- apps/link/components/StyledDataGrid.tsx | 84 +++++++++++++++++++ apps/link/components/TicketList.tsx | 71 ++++++++++++++++ .../graphql/getTicketOverviewCountsQuery.ts | 19 +++++ .../link/graphql/getTicketsByOverviewQuery.ts | 60 +++++++++++++ apps/link/next.config.js | 7 +- apps/link/pages/_app.tsx | 4 +- apps/link/pages/tickets/assigned.tsx | 53 ++++++------ apps/link/pages/tickets/pending.tsx | 53 ++++++------ apps/link/pages/tickets/unassigned.tsx | 53 ++++++------ apps/link/pages/tickets/urgent.tsx | 53 ++++++------ docker-compose.yml | 5 +- 12 files changed, 397 insertions(+), 133 deletions(-) create mode 100644 apps/link/components/StyledDataGrid.tsx create mode 100644 apps/link/components/TicketList.tsx create mode 100644 apps/link/graphql/getTicketOverviewCountsQuery.ts create mode 100644 apps/link/graphql/getTicketsByOverviewQuery.ts diff --git a/apps/link/components/Sidebar.tsx b/apps/link/components/Sidebar.tsx index 6760d62..281b745 100644 --- a/apps/link/components/Sidebar.tsx +++ b/apps/link/components/Sidebar.tsx @@ -1,4 +1,5 @@ import { FC, useState } from "react"; +import useSWR from "swr"; import { Box, Grid, @@ -19,12 +20,14 @@ import { Cottage as CottageIcon, Settings as SettingsIcon, ExpandCircleDown as ExpandCircleDownIcon, + Dvr as DvrIcon, } from "@mui/icons-material"; import { useRouter } from "next/router"; import Link from "next/link"; import Image from "next/image"; import LinkLogo from "public/link-logo-small.png"; -import { useSession } from "next-auth/react"; +import { useSession, signOut } from "next-auth/react"; +import { getTicketOverviewCountsQuery } from "graphql/getTicketOverviewCountsQuery"; const openWidth = 270; const closedWidth = 100; @@ -38,8 +41,9 @@ const MenuItem = ({ selected = false, open = true, badge, + target = "_self", }: any) => ( - + )} - {badge && ( + {badge && badge > 0 ? ( - )} + ) : null} ); @@ -153,9 +157,29 @@ interface SidebarProps { } export const Sidebar: FC = ({ open, setOpen }) => { - const { pathname } = useRouter(); + const router = useRouter(); + const { pathname } = router; const { data: session } = useSession(); const username = session?.user?.name || "User"; + const { data: overviewData, error: overviewError }: any = useSWR( + { + document: getTicketOverviewCountsQuery, + }, + { refreshInterval: 10000 } + ); + const findOverviewCountByID = (id: number) => + overviewData?.ticketOverviews?.edges?.find((overview: any) => + overview.node.id.endsWith(`/${id}`) + )?.node?.ticketCount ?? 0; + const assignedCount = findOverviewCountByID(1); + const urgentCount = findOverviewCountByID(7); + const pendingCount = findOverviewCountByID(3); + const unassignedCount = findOverviewCountByID(2); + console.log({ assignedCount, urgentCount, pendingCount, unassignedCount }); + + const logout = () => { + signOut({ callbackUrl: "/login" }); + }; return ( = ({ open, setOpen }) => { /> -{/* = ({ open, setOpen }) => { Icon={FeaturedPlayListIcon} iconSize={0} selected={pathname.endsWith("/tickets/assigned")} - badge={3} + badge={assignedCount} open={open} /> = ({ open, setOpen }) => { Icon={FeaturedPlayListIcon} iconSize={0} selected={pathname.endsWith("/tickets/urgent")} - badge={1} + badge={urgentCount} open={open} /> = ({ open, setOpen }) => { Icon={FeaturedPlayListIcon} iconSize={0} selected={pathname.endsWith("/tickets/pending")} - badge={9} + badge={pendingCount} open={open} /> = ({ open, setOpen }) => { href="/tickets/unassigned" Icon={FeaturedPlayListIcon} iconSize={0} - selected={pathname.endsWith("/tickets/unnassigned")} - badge={27} - open={open} - /> - -*/ } = ({ open, setOpen }) => { onClick={undefined} > -{/* + {/* = ({ open, setOpen }) => { /> - + diff --git a/apps/link/components/StyledDataGrid.tsx b/apps/link/components/StyledDataGrid.tsx new file mode 100644 index 0000000..f594f93 --- /dev/null +++ b/apps/link/components/StyledDataGrid.tsx @@ -0,0 +1,84 @@ +import { FC } from "react"; +import { Box } from "@mui/material"; +import { + DataGridPro, + GridColDef, + GridColumnVisibilityModel, + GridRowSelectionModel, +} from "@mui/x-data-grid-pro"; +import { useCookies } from "react-cookie"; + +interface StyledDataGridProps { + name: string; + columns: GridColDef[]; + rows: any[]; + // eslint-disable-next-line no-unused-vars + onRowClick?: (row: any) => void; + height?: string; + selectedRows?: GridRowSelectionModel; + // eslint-disable-next-line no-unused-vars + setSelectedRows?: (rows: GridRowSelectionModel) => void; +} + +export const StyledDataGrid: FC = ({ + name, + columns, + rows, + onRowClick, + height = "calc(100vh - 20px)", + selectedRows, + setSelectedRows, +}) => { + const cookieName = `${name}DataGridColumnState`; + const [cookies, setCookie] = useCookies([cookieName]); + const handleColumnVisibilityChange = (model: GridColumnVisibilityModel) => + setCookie(cookieName, model, { path: "/" }); + + return ( + + + + ); +}; diff --git a/apps/link/components/TicketList.tsx b/apps/link/components/TicketList.tsx new file mode 100644 index 0000000..25d457e --- /dev/null +++ b/apps/link/components/TicketList.tsx @@ -0,0 +1,71 @@ +import { FC } from "react"; +import { Grid, Box } from "@mui/material"; +import { GridColDef } from "@mui/x-data-grid-pro"; +import { StyledDataGrid } from "./StyledDataGrid"; +import { typography } from "styles/theme"; +import { useRouter } from "next/router"; + +interface TicketListProps { + title: string; + tickets: any; +} + +export const TicketList: FC = ({ title, tickets }) => { + const router = useRouter(); + let gridColumns: GridColDef[] = [ + { + field: "number", + headerName: "Number", + flex: 1, + }, + { + field: "title", + headerName: "Title", + flex: 1, + }, + { + field: "customer", + headerName: "Sender", + valueGetter: (params) => params.row?.customer?.fullname, + flex: 1, + }, + { + field: "group", + headerName: "Group", + valueGetter: (params) => params.row?.group?.name, + flex: 1, + }, + ]; + console.log({ tickets }); + const rowClick = ({ row }) => { + router.push(`/tickets/${row.internalId}`); + }; + + return ( + + + + + {title} + + + + + + + + ); +}; diff --git a/apps/link/graphql/getTicketOverviewCountsQuery.ts b/apps/link/graphql/getTicketOverviewCountsQuery.ts new file mode 100644 index 0000000..ecd656d --- /dev/null +++ b/apps/link/graphql/getTicketOverviewCountsQuery.ts @@ -0,0 +1,19 @@ +import { gql } from 'graphql-request'; + +export const getTicketOverviewCountsQuery = gql` +query ticketOverviewTicketCount { + ticketOverviews { + edges { + node { + id + name + ticketCount + } + cursor + } + pageInfo { + endCursor + hasNextPage + } + } +}`; diff --git a/apps/link/graphql/getTicketsByOverviewQuery.ts b/apps/link/graphql/getTicketsByOverviewQuery.ts new file mode 100644 index 0000000..2a71e35 --- /dev/null +++ b/apps/link/graphql/getTicketsByOverviewQuery.ts @@ -0,0 +1,60 @@ +import { gql } from 'graphql-request'; + +export const getTicketsByOverviewQuery = gql` +query ticketsByOverview($overviewId: ID!, $orderBy: String, $orderDirection: EnumOrderDirection, $cursor: String, $showPriority: Boolean = false, $showUpdatedBy: Boolean = false, $pageSize: Int = 10) { + ticketsByOverview( + overviewId: $overviewId + orderBy: $orderBy + orderDirection: $orderDirection + after: $cursor + first: $pageSize + ) { + totalCount + edges { + node { + id + internalId + number + title + createdAt + updatedAt + updatedBy @include(if: $showUpdatedBy) { + id + fullname + } + customer { + id + firstname + lastname + fullname + } + organization { + id + name + } + state { + id + name + stateType { + name + } + } + group { + id + name + } + priority @include(if: $showPriority) { + id + name + uiColor + defaultCreate + } + } + cursor + } + pageInfo { + endCursor + hasNextPage + } + } +}`; diff --git a/apps/link/next.config.js b/apps/link/next.config.js index bf8de36..c2b2767 100644 --- a/apps/link/next.config.js +++ b/apps/link/next.config.js @@ -5,17 +5,18 @@ const nextConfig = { linkURL: process.env.LINK_URL, leafcutterURL: process.env.LEAFCUTTER_URL, metamigoURL: process.env.METAMIGO_URL, + muiLicenseKey: process.env.MUI_LICENSE_KEY, }, async rewrites() { return { fallback: [ { - source: '/:path*', + source: "/:path*", destination: `/proxy/zammad/:path*`, }, ], - } - } + }; + }, }; module.exports = nextConfig; diff --git a/apps/link/pages/_app.tsx b/apps/link/pages/_app.tsx index 754b65c..2712240 100644 --- a/apps/link/pages/_app.tsx +++ b/apps/link/pages/_app.tsx @@ -16,8 +16,10 @@ import "styles/global.css"; import { LicenseInfo } from "@mui/x-data-grid-pro"; import { SWRConfig } from "swr"; import { GraphQLClient } from "graphql-request"; +import getConfig from "next/config"; -LicenseInfo.setLicenseKey(process.env.MUI_LICENSE_KEY); +const { publicRuntimeConfig } = getConfig(); +LicenseInfo.setLicenseKey(publicRuntimeConfig.muiLicenseKey); const clientSideEmotionCache: any = createEmotionCache(); diff --git a/apps/link/pages/tickets/assigned.tsx b/apps/link/pages/tickets/assigned.tsx index a176888..18b775a 100644 --- a/apps/link/pages/tickets/assigned.tsx +++ b/apps/link/pages/tickets/assigned.tsx @@ -1,31 +1,32 @@ -import { FC } from "react"; import Head from "next/head"; -import { Grid } from "@mui/material"; +import useSWR from "swr"; +import { NextPage } from "next"; import { Layout } from "components/Layout"; -import { ZammadWrapper } from "components/ZammadWrapper"; +import { TicketList } from "components/TicketList"; +import { getTicketsByOverviewQuery } from "graphql/getTicketsByOverviewQuery"; -const Assigned: FC = () => ( - - - Link Shell - - - - - - - -); +const Assigned: NextPage = () => { + const { data: ticketData, error: ticketError }: any = useSWR( + { + document: getTicketsByOverviewQuery, + variables: { overviewId: "gid://zammad/Overview/1" }, + }, + { refreshInterval: 10000 } + ); + + const shouldRender = !ticketError && ticketData; + const tickets = + ticketData?.ticketsByOverview?.edges.map((edge: any) => edge.node) || []; + + return ( + + + Link Shell – Assigned Tickets + + {shouldRender && } + {ticketError &&
{ticketError.toString()}
} +
+ ); +}; export default Assigned; diff --git a/apps/link/pages/tickets/pending.tsx b/apps/link/pages/tickets/pending.tsx index c09a0a7..b275df5 100644 --- a/apps/link/pages/tickets/pending.tsx +++ b/apps/link/pages/tickets/pending.tsx @@ -1,31 +1,32 @@ -import { FC } from "react"; import Head from "next/head"; -import { Grid } from "@mui/material"; +import useSWR from "swr"; +import { NextPage } from "next"; import { Layout } from "components/Layout"; -import { ZammadWrapper } from "components/ZammadWrapper"; +import { TicketList } from "components/TicketList"; +import { getTicketsByOverviewQuery } from "graphql/getTicketsByOverviewQuery"; -const Pending: FC = () => ( - - - Link Shell - - - - - - - -); +const Pending: NextPage = () => { + const { data: ticketData, error: ticketError }: any = useSWR( + { + document: getTicketsByOverviewQuery, + variables: { overviewId: "gid://zammad/Overview/3" }, + }, + { refreshInterval: 10000 } + ); + + const shouldRender = !ticketError && ticketData; + const tickets = + ticketData?.ticketsByOverview?.edges.map((edge: any) => edge.node) || []; + + return ( + + + Link Shell – Assigned Tickets + + {shouldRender && } + {ticketError &&
{ticketError.toString()}
} +
+ ); +}; export default Pending; diff --git a/apps/link/pages/tickets/unassigned.tsx b/apps/link/pages/tickets/unassigned.tsx index beb58d9..271b3dc 100644 --- a/apps/link/pages/tickets/unassigned.tsx +++ b/apps/link/pages/tickets/unassigned.tsx @@ -1,31 +1,32 @@ -import { FC } from "react"; import Head from "next/head"; -import { Grid } from "@mui/material"; +import useSWR from "swr"; +import { NextPage } from "next"; import { Layout } from "components/Layout"; -import { ZammadWrapper } from "components/ZammadWrapper"; +import { TicketList } from "components/TicketList"; +import { getTicketsByOverviewQuery } from "graphql/getTicketsByOverviewQuery"; -const Unassigned: FC = () => ( - - - Link Shell - - - - - - - -); +const Unassigned: NextPage = () => { + const { data: ticketData, error: ticketError }: any = useSWR( + { + document: getTicketsByOverviewQuery, + variables: { overviewId: "gid://zammad/Overview/2" }, + }, + { refreshInterval: 10000 } + ); + + const shouldRender = !ticketError && ticketData; + const tickets = + ticketData?.ticketsByOverview?.edges.map((edge: any) => edge.node) || []; + + return ( + + + Link Shell – Assigned Tickets + + {shouldRender && } + {ticketError &&
{ticketError.toString()}
} +
+ ); +}; export default Unassigned; diff --git a/apps/link/pages/tickets/urgent.tsx b/apps/link/pages/tickets/urgent.tsx index 9303d0b..443ec6c 100644 --- a/apps/link/pages/tickets/urgent.tsx +++ b/apps/link/pages/tickets/urgent.tsx @@ -1,31 +1,32 @@ -import { FC } from "react"; import Head from "next/head"; -import { Grid } from "@mui/material"; +import useSWR from "swr"; +import { NextPage } from "next"; import { Layout } from "components/Layout"; -import { ZammadWrapper } from "components/ZammadWrapper"; +import { TicketList } from "components/TicketList"; +import { getTicketsByOverviewQuery } from "graphql/getTicketsByOverviewQuery"; -const Urgent: FC = () => ( - - - Link Shell - - - - - - - -); +const Urgent: NextPage = () => { + const { data: ticketData, error: ticketError }: any = useSWR( + { + document: getTicketsByOverviewQuery, + variables: { overviewId: "gid://zammad/Overview/7" }, + }, + { refreshInterval: 10000 } + ); + + const shouldRender = !ticketError && ticketData; + const tickets = + ticketData?.ticketsByOverview?.edges.map((edge: any) => edge.node) || []; + + return ( + + + Link Shell – Urgent Tickets + + {shouldRender && } + {ticketError &&
{ticketError.toString()}
} +
+ ); +}; export default Urgent; diff --git a/docker-compose.yml b/docker-compose.yml index 0335b4b..33fafb2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -214,6 +214,7 @@ services: link: container_name: link + restart: ${RESTART} build: context: . dockerfile: ./apps/link/Dockerfile @@ -223,13 +224,12 @@ services: - "8003:3000" environment: ZAMMAD_PROXY_URL: ${ZAMMAD_PROXY_URL} - ZAMMAD_URL: ${ZAMMAD_URL} ZAMMAD_API_TOKEN: ${ZAMMAD_API_TOKEN} ZAMMAD_VIRUAL_HOST: ${ZAMMAD_VIRTUAL_HOST} LINK_URL: ${LINK_URL} LEAFCUTTER_URL: http://leafcutter:3000 METAMIGO_URL: http://metamigo-frontend:3000 - ZAMMAD_URL: http://zammad-nginx:8080 + ZAMMAD_URL: http://localhost:8001 NEXTAUTH_URL: ${LINK_URL} NEXTAUTH_SECRET: ${NEXTAUTH_SECRET} NEXTAUTH_AUDIENCE: ${NEXTAUTH_AUDIENCE} @@ -240,6 +240,7 @@ services: leafcutter: container_name: leafcutter + restart: ${RESTART} build: context: . dockerfile: ./apps/leafcutter/Dockerfile