App directory refactoring

This commit is contained in:
Darren Clarke 2023-06-26 10:07:12 +00:00 committed by GitHub
parent a53a26f4c0
commit b312a8c862
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
153 changed files with 1532 additions and 1447 deletions

View file

@ -0,0 +1,31 @@
"use client";
import { FC } from "react";
import Link from "next/link";
import { Button as MUIButton } from "@mui/material";
interface ButtonProps {
text: string;
color: string;
href: string;
}
export const Button: FC<ButtonProps> = ({ text, color, href }) => (
<Link href={href} passHref>
<MUIButton
variant="contained"
disableElevation
sx={{
fontFamily: "Poppins, sans-serif",
fontWeight: 700,
borderRadius: 999,
backgroundColor: color,
padding: "6px 30px",
margin: "20px 0px",
whiteSpace: "nowrap",
}}
>
{text}
</MUIButton>
</Link>
);

View file

@ -0,0 +1,22 @@
"use client";
import { FC } from "react";
import { Box, Grid } from "@mui/material";
type DisplayErrorProps = {
error: Error;
};
export const DisplayError: FC<DisplayErrorProps> = ({ error }) => (
<Grid
container
direction="column"
justifyContent="space-around"
alignItems="center"
style={{ height: 600, width: "100%", color: "red", fontSize: 20 }}
>
<Grid item>
<Box>{`Error: ${error.message}`}</Box>
</Grid>
</Grid>
);

View file

@ -0,0 +1,6 @@
"use client";
import { FC } from "react";
import { ZammadWrapper } from "@/app/_components/ZammadWrapper";
export const Home: FC = () => <ZammadWrapper path="/#dashboard" hideSidebar />;

View file

@ -0,0 +1,21 @@
"use client";
import { FC, PropsWithChildren, useState } from "react";
import { Grid } from "@mui/material";
import { Sidebar } from "./Sidebar";
export const InternalLayout: FC<PropsWithChildren> = ({ children }) => {
const [open, setOpen] = useState(true);
return (
<Grid container direction="row">
<Sidebar open={open} setOpen={setOpen} />
<Grid
item
sx={{ ml: open ? "270px" : "100px", width: "100%", height: "100vh" }}
>
{children as any}
</Grid>
</Grid>
);
};

View file

@ -0,0 +1,57 @@
"use client";
import { FC, PropsWithChildren, useState } from "react";
import { CssBaseline } from "@mui/material";
import { CookiesProvider } from "react-cookie";
import { SessionProvider } from "next-auth/react";
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
import { SWRConfig } from "swr";
import { GraphQLClient } from "graphql-request";
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
export const MultiProvider: FC<PropsWithChildren> = ({ children }) => {
const [csrfToken, setCsrfToken] = useState("");
const origin =
typeof window !== "undefined" && window.location.origin
? window.location.origin
: null;
const client = new GraphQLClient(`${origin}/proxy/zammad/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 (
<>
<CssBaseline />
<NextAppDirEmotionCacheProvider options={{ key: "css" }}>
<SWRConfig value={{ fetcher: graphQLFetcher }}>
<SessionProvider>
<CookiesProvider>
<LocalizationProvider dateAdapter={AdapterDateFns}>
{children}
</LocalizationProvider>
</CookiesProvider>
</SessionProvider>
</SWRConfig>
</NextAppDirEmotionCacheProvider>
</>
);
};

View file

@ -0,0 +1,554 @@
"use client";
import { FC, useState } from "react";
import useSWR from "swr";
import {
Box,
Grid,
Typography,
List,
ListItemButton,
ListItemIcon,
ListItemText,
ListItemSecondaryAction,
Drawer,
Collapse,
} from "@mui/material";
import {
FeaturedPlayList as FeaturedPlayListIcon,
Person as PersonIcon,
Analytics as AnalyticsIcon,
Logout as LogoutIcon,
Cottage as CottageIcon,
Settings as SettingsIcon,
ExpandCircleDown as ExpandCircleDownIcon,
Dvr as DvrIcon,
} from "@mui/icons-material";
import { usePathname } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import LinkLogo from "public/link-logo-small.png";
import { useSession, signOut } from "next-auth/react";
import { getTicketOverviewCountsQuery } from "@/app/_graphql/getTicketOverviewCountsQuery";
const openWidth = 270;
const closedWidth = 100;
const MenuItem = ({
name,
href,
Icon,
iconSize,
inset = false,
selected = false,
open = true,
badge,
target = "_self",
}: any) => (
<Link href={href} target={target}>
<ListItemButton
sx={{
p: 0,
mb: 1,
bl: iconSize === 0 ? "1px solid white" : "inherit",
}}
selected={selected}
>
{iconSize > 0 ? (
<ListItemIcon
sx={{
color: `white`,
minWidth: 0,
mr: 2,
textAlign: "center",
margin: open ? "0 8 0 0" : "0 auto",
}}
>
<Box
sx={{
width: iconSize,
height: iconSize,
mr: 0.5,
mt: "-4px",
}}
>
<Icon />
</Box>
</ListItemIcon>
) : (
<Box
sx={{
width: 30,
height: "28px",
position: "relative",
ml: "9px",
mr: "1px",
}}
>
<Box
sx={{
width: "1px",
height: "56px",
backgroundColor: "white",
position: "absolute",
left: "3px",
top: "-10px",
}}
/>
<Box
sx={{
width: "42px",
height: "42px",
position: "absolute",
top: "-27px",
left: "3px",
border: "solid 1px #fff",
borderColor: "transparent transparent transparent #fff",
borderRadius: "60px",
rotate: "-35deg",
}}
/>
</Box>
)}
{open && (
<ListItemText
inset={inset}
primary={
<Typography
variant="body1"
sx={{
fontSize: 16,
fontFamily: "Roboto",
fontWeight: "bold",
border: 0,
textAlign: "left",
color: "white",
}}
>
{name}
</Typography>
}
/>
)}
{badge && badge > 0 ? (
<ListItemSecondaryAction>
<Typography
color="textSecondary"
variant="body1"
className="badge"
sx={{
backgroundColor: "#FFB620",
color: "black !important",
borderRadius: 10,
px: 1,
fontSize: 12,
fontWeight: "bold",
}}
>
{badge}
</Typography>
</ListItemSecondaryAction>
) : null}
</ListItemButton>
</Link>
);
interface SidebarProps {
open: boolean;
setOpen: (open: boolean) => void;
}
export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
const pathname = usePathname();
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);
const logout = () => {
signOut({ callbackUrl: "/login" });
};
return (
<Drawer
sx={{ width: open ? openWidth : closedWidth, flexShrink: 0 }}
variant="permanent"
anchor="left"
open={open}
PaperProps={{
sx: {
width: open ? openWidth : closedWidth,
border: 0,
overflow: "visible",
},
}}
>
<Box
sx={{
position: "absolute",
top: 20,
right: open ? -8 : -16,
color: "#1C75FD",
rotate: open ? "90deg" : "-90deg",
}}
onClick={() => {
setOpen!(!open);
}}
>
<ExpandCircleDownIcon
sx={{
width: 30,
height: 30,
background: "white",
borderRadius: 500,
}}
/>
</Box>
<Grid
container
direction="column"
justifyContent="space-between"
wrap="nowrap"
sx={{ backgroundColor: "#25272A", height: "100%", p: 2 }}
>
<Grid item container>
<Grid item sx={{ width: open ? "40px" : "100%" }}>
<Box
sx={{
width: "40px",
height: "40px",
margin: open ? "0" : "0 auto",
}}
>
<Image
src={LinkLogo}
alt="Link logo"
width={40}
height={40}
style={{
objectFit: "cover",
filter: "grayscale(100) brightness(100)",
}}
/>
</Box>
.
</Grid>
{open && (
<Grid item>
<Typography
variant="h2"
sx={{
fontSize: 26,
color: "white",
fontWeight: 700,
mt: 1,
ml: 0.5,
fontFamily: "Poppins",
}}
>
CDR Link
</Typography>
</Grid>
)}
</Grid>
<Grid item>
<Box
sx={{
height: "0.5px",
width: "100%",
backgroundColor: "#666",
mb: 1,
}}
/>
</Grid>
<Grid item>
<Typography
variant="h6"
sx={{
fontSize: 12,
color: "#999",
fontWeight: "bold",
textAlign: open ? "left" : "center",
}}
>
Hello
</Typography>
</Grid>
<Grid item>
<Typography
variant="h2"
sx={{
fontSize: 22,
color: "white",
mb: 1.5,
fontWeight: "bold",
textAlign: open ? "left" : "center",
}}
>
{open
? username
: username
.split(" ")
.map((name) => name.substring(0, 1))
.join("")}
</Typography>
</Grid>
<Grid item>
<Box
sx={{ height: "0.5px", width: "100%", backgroundColor: "#666" }}
/>
</Grid>
<Grid item container direction="column" sx={{ mt: "6px" }} flexGrow={1}>
<List
component="nav"
sx={{
a: {
textDecoration: "none",
".MuiListItemButton-root": {
p: 1,
borderRadius: 2,
"&:hover": {
background: "#555",
},
".MuiTypography-root": {
p: {
color: "#999 !important",
fontSize: 16,
},
},
".badge": {
p: { fontSize: 12, color: "black !important" },
},
},
".Mui-selected": {
background: "#444",
color: "#fff !important",
".MuiTypography-root": {
p: {
color: "#fff !important",
fontSize: 16,
},
},
".badge": {
p: { fontSize: 12, color: "black !important" },
},
},
},
}}
>
<MenuItem
name="Home"
href="/"
Icon={CottageIcon}
iconSize={20}
selected={pathname.endsWith("/")}
open={open}
/>
<MenuItem
name="Tickets"
href="/overview/assigned"
Icon={FeaturedPlayListIcon}
selected={
pathname.startsWith("/overview") ||
pathname.startsWith("/tickets")
}
iconSize={20}
open={open}
/>
<Collapse
in={
pathname.startsWith("/overview") ||
pathname.startsWith("/tickets")
}
timeout="auto"
unmountOnExit
onClick={undefined}
>
<List component="div" disablePadding>
<MenuItem
name="Assigned"
href="/overview/assigned"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/overview/assigned")}
badge={assignedCount}
open={open}
/>
<MenuItem
name="Urgent"
href="/overview/urgent"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/overview/urgent")}
badge={urgentCount}
open={open}
/>
<MenuItem
name="Pending"
href="/overview/pending"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/overview/pending")}
badge={pendingCount}
open={open}
/>
<MenuItem
name="Unassigned"
href="/overview/unassigned"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/overview/unassigned")}
badge={unassignedCount}
open={open}
/>
</List>
</Collapse>
<MenuItem
name="Knowledge Base"
href="/knowledge"
Icon={CottageIcon}
iconSize={20}
selected={pathname.endsWith("/knowledge")}
open={open}
/>
<MenuItem
name="Leafcutter"
href="/leafcutter/about"
Icon={AnalyticsIcon}
iconSize={20}
selected={pathname.endsWith("/leafcutter")}
open={open}
/>
<Collapse
in={pathname.startsWith("/leafcutter")}
timeout="auto"
unmountOnExit
onClick={undefined}
>
<List component="div" disablePadding>
{/*
<MenuItem
name="Dashboard"
href="/leafcutter"
iconSize={0}
selected={pathname.endsWith("/leafcutter")}
open={open}
/>
<MenuItem
name="Search and Create"
href="/leafcutter/create"
iconSize={0}
selected={pathname.endsWith("/leafcutter/create")}
open={open}
/>
<MenuItem
name="Trends"
href="/leafcutter/trends"
iconSize={0}
selected={pathname.endsWith("/leafcutter/trends")}
open={open}
/>
*/}
<MenuItem
name="FAQ"
href="/leafcutter/faq"
iconSize={0}
selected={pathname.endsWith("/leafcutter/faq")}
open={open}
/>
<MenuItem
name="About"
href="/leafcutter/about"
Icon={AnalyticsIcon}
iconSize={0}
selected={pathname.endsWith("/leafcutter/about")}
open={open}
/>
</List>
</Collapse>
<MenuItem
name="Profile"
href="/profile"
Icon={PersonIcon}
iconSize={20}
selected={pathname.endsWith("/profile")}
open={open}
/>
<MenuItem
name="Admin"
href="/admin/zammad"
Icon={SettingsIcon}
iconSize={20}
open={open}
/>
<Collapse
in={pathname.startsWith("/admin/")}
timeout="auto"
unmountOnExit
onClick={undefined}
>
<List component="div" disablePadding>
<MenuItem
name="Zammad Settings"
href="/admin/zammad"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/zammad")}
open={open}
/>
<MenuItem
name="Metamigo"
href="/admin/metamigo"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/metamigo")}
open={open}
/>
<MenuItem
name="Label Studio"
href="/admin/label-studio"
Icon={FeaturedPlayListIcon}
iconSize={0}
selected={pathname.endsWith("/admin/label-studio")}
open={open}
/>
</List>
</Collapse>
<MenuItem
name="Zammad Interface"
href="/proxy/zammad"
Icon={DvrIcon}
iconSize={20}
open={open}
target="_blank"
/>
<MenuItem
name="Logout"
href="/logout"
Icon={LogoutIcon}
iconSize={20}
open={open}
onClick={logout}
/>
</List>
</Grid>
</Grid>
</Drawer>
);
};

View file

@ -0,0 +1,86 @@
"use client";
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<StyledDataGridProps> = ({
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 (
<Box
sx={{
backgroundColor: "#ddd",
border: 0,
width: "100%",
height,
".MuiDataGrid-row:nth-of-type(1n)": {
backgroundColor: "#f3f3f3",
},
".MuiDataGrid-row:nth-of-type(2n)": {
backgroundColor: "#fff",
},
".MuiDataGrid-columnHeaderTitle": {
color: "#333",
fontWeight: "bold",
fontSize: 11,
margin: "0 auto",
},
".MuiDataGrid-columnHeader": {
backgroundColor: "#ccc",
border: 0,
borderBottom: "3px solid #ddd",
},
}}
>
<DataGridPro
rows={rows}
columns={columns}
density="compact"
hideFooter
sx={{ height }}
rowBuffer={30}
checkboxSelection={!!setSelectedRows}
onRowSelectionModelChange={setSelectedRows}
rowSelectionModel={selectedRows}
rowHeight={46}
scrollbarSize={0}
disableVirtualization
columnVisibilityModel={
(cookies[cookieName] as GridColumnVisibilityModel) ?? undefined
}
onColumnVisibilityModelChange={handleColumnVisibilityChange}
onRowClick={onRowClick}
/>
</Box>
);
};

View file

@ -0,0 +1,88 @@
"use client";
import { FC, useState } from "react";
import getConfig from "next/config";
import { useRouter } from "next/navigation";
import Iframe from "react-iframe";
type ZammadWrapperProps = {
path: string;
hideSidebar?: boolean;
};
export const ZammadWrapper: FC<ZammadWrapperProps> = ({
path,
hideSidebar = true,
}) => {
const router = useRouter();
const [display, setDisplay] = useState("inherit");
//const {
// publicRuntimeConfig: { linkURL },
// } = getConfig();
const linkURL = "http://localhost:3000";
const url = `${linkURL}/proxy/zammad${path}`;
console.log({ url });
return (
// @ts-ignore
<Iframe
id="zammad"
url={url}
width="100%"
height="100%"
frameBorder={0}
styles={{ display }}
onLoad={() => {
const linkElement = document.querySelector("iframe");
// const baseElement = linkElement.contentDocument.createElement("base");
// baseElement.href = `${linkURL}/proxy/zammad`;
if (
linkElement.contentDocument &&
linkElement.contentDocument?.querySelector &&
linkElement.contentDocument.querySelector("#navigation") &&
linkElement.contentDocument.querySelector("body") &&
linkElement.contentDocument.querySelector(".sidebar")
) {
// @ts-ignore
linkElement.contentDocument.querySelector("#navigation").style =
"display: none";
// @ts-ignore
linkElement.contentDocument.querySelector("body").style =
"font-family: Arial";
if (hideSidebar) {
// @ts-ignore
linkElement.contentDocument.querySelector(".sidebar").style =
"display: none";
}
// @ts-ignore
if (linkElement.contentDocument.querySelector(".overview-header")) {
// @ts-ignore
(
linkElement.contentDocument.querySelector(
".overview-header"
) as any
).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);
}
});
}
}
}}
/>
);
};