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 }) => {
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