From b6210141789d99cd02308cd7388aae6a88f3f095 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Mon, 9 Sep 2024 10:19:36 +0200 Subject: [PATCH 1/7] Link mobile layout --- .../app/(main)/_components/InternalLayout.tsx | 105 +++++++++++++++++- apps/link/app/(main)/_components/Sidebar.tsx | 21 ++-- apps/link/app/(main)/tickets/[id]/layout.tsx | 6 +- packages/ui/components/List.tsx | 7 +- 4 files changed, 116 insertions(+), 23 deletions(-) diff --git a/apps/link/app/(main)/_components/InternalLayout.tsx b/apps/link/app/(main)/_components/InternalLayout.tsx index e80b52a..36062cd 100644 --- a/apps/link/app/(main)/_components/InternalLayout.tsx +++ b/apps/link/app/(main)/_components/InternalLayout.tsx @@ -1,9 +1,29 @@ "use client"; -import { FC, PropsWithChildren, useState } from "react"; -import { Grid, Box } from "@mui/material"; -import { Sidebar } from "./Sidebar"; +import { FC, PropsWithChildren, useEffect, useState } from "react"; import { SetupModeWarning } from "./SetupModeWarning"; +import { + AppBar as MuiAppBar, + ToolbarProps, + Box, + Grid, + IconButton, + styled, + Toolbar, + Typography, + Menu, + useTheme, + useMediaQuery, +} from "@mui/material"; +import { Sidebar } from "./Sidebar"; +import { + ExpandCircleDown, + MenuBookTwoTone, + MenuSharp, +} from "@mui/icons-material"; +import { fonts } from "@link-stack/ui"; +import Image from "next/image"; +import LinkLogo from "public/link-logo-small.png"; interface InternalLayoutProps extends PropsWithChildren { setupModeActive: boolean; @@ -15,20 +35,93 @@ export const InternalLayout: FC = ({ setupModeActive, leafcutterEnabled, }) => { - const [open, setOpen] = useState(true); + const { poppins } = fonts; + + const theme = useTheme(); + const mobile = useMediaQuery(theme.breakpoints.down("sm")); + + const [open, setOpen] = useState(false); + const [openWidth, setOpenWidth] = useState(0); + const [closedWidth, setClosedWidth] = useState(0); + + useEffect(() => { + setClosedWidth(mobile ? 0 : 70); + setOpenWidth(270); + setOpen(!mobile); + }, [mobile]); + + interface HeaderBarProps extends ToolbarProps { + open?: boolean; + } + + const HeaderBar = styled(Toolbar, { + shouldForwardProp: (prop) => prop !== "open", + })(({ theme, open }) => ({ + backgroundColor: "#25272A", + width: "100%", + height: "56px", + })); return ( + {mobile ? ( + + setOpen(true)} + > + + + + CDR Link + + + Link logo + + + ) : (null)} {children as any} diff --git a/apps/link/app/(main)/_components/Sidebar.tsx b/apps/link/app/(main)/_components/Sidebar.tsx index 6c8af30..8c50799 100644 --- a/apps/link/app/(main)/_components/Sidebar.tsx +++ b/apps/link/app/(main)/_components/Sidebar.tsx @@ -12,6 +12,8 @@ import { ListItemSecondaryAction, Drawer, Collapse, + useTheme, + useMediaQuery, } from "@mui/material"; import { FeaturedPlayList as FeaturedPlayListIcon, @@ -35,9 +37,6 @@ import { getOverviewTicketCountsAction } from "app/_actions/overviews"; import { SearchBox } from "./SearchBox"; import { fonts } from "@link-stack/ui"; -const openWidth = 270; -const closedWidth = 70; - const MenuItem = ({ name, href, @@ -178,14 +177,12 @@ const MenuItem = ({ interface SidebarProps { open: boolean; setOpen: (open: boolean) => void; + openWidth: number; + closedWidth: number; leafcutterEnabled?: boolean; } -export const Sidebar: FC = ({ - open, - setOpen, - leafcutterEnabled = false, -}) => { +export const Sidebar: FC = ({ open, setOpen, openWidth, closedWidth, leafcutterEnabled = false }) => { const pathname = usePathname(); const { data: session } = useSession(); const [overviewCounts, setOverviewCounts] = useState(null); @@ -207,6 +204,9 @@ export const Sidebar: FC = ({ return () => clearInterval(interval); }, []); + const theme = useTheme(); + const mobile = useMediaQuery(theme.breakpoints.down('sm')); + const logout = () => { signOut({ callbackUrl: "/login" }); }; @@ -214,14 +214,15 @@ export const Sidebar: FC = ({ return ( {if (mobile) setOpen(false)}} PaperProps={{ sx: { width: open ? openWidth : closedWidth, border: 0, - overflow: "visible", + overflow: open ? "visible" : "hidden", }, }} > diff --git a/apps/link/app/(main)/tickets/[id]/layout.tsx b/apps/link/app/(main)/tickets/[id]/layout.tsx index 35ce25b..d3ba633 100644 --- a/apps/link/app/(main)/tickets/[id]/layout.tsx +++ b/apps/link/app/(main)/tickets/[id]/layout.tsx @@ -12,11 +12,11 @@ type LayoutProps = { export default function Layout({ detail, edit, params: { id } }: LayoutProps) { return ( - - + + {detail} - + {edit} diff --git a/packages/ui/components/List.tsx b/packages/ui/components/List.tsx index 02a4a26..68b79a8 100644 --- a/packages/ui/components/List.tsx +++ b/packages/ui/components/List.tsx @@ -36,8 +36,8 @@ export const List: FC = ({ }; return ( - - + + = ({ {buttons} - + Date: Mon, 9 Sep 2024 10:24:05 +0200 Subject: [PATCH 2/7] Hide overflow during load --- apps/link/app/(main)/_components/Sidebar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/link/app/(main)/_components/Sidebar.tsx b/apps/link/app/(main)/_components/Sidebar.tsx index 8c50799..1ef9910 100644 --- a/apps/link/app/(main)/_components/Sidebar.tsx +++ b/apps/link/app/(main)/_components/Sidebar.tsx @@ -222,7 +222,7 @@ export const Sidebar: FC = ({ open, setOpen, openWidth, closedWidt sx: { width: open ? openWidth : closedWidth, border: 0, - overflow: open ? "visible" : "hidden", + overflow: openWidth == 0 ? "hidden" : "visible", }, }} > From 89fdd955fe4fc651f71bddabf260a85d2088dce4 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Mon, 9 Sep 2024 10:24:15 +0200 Subject: [PATCH 3/7] Fix padding on mobile --- apps/link/app/(main)/_components/ZammadWrapper.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apps/link/app/(main)/_components/ZammadWrapper.tsx b/apps/link/app/(main)/_components/ZammadWrapper.tsx index 1696040..975eb26 100644 --- a/apps/link/app/(main)/_components/ZammadWrapper.tsx +++ b/apps/link/app/(main)/_components/ZammadWrapper.tsx @@ -115,6 +115,14 @@ export const ZammadWrapper: FC = ({ // @ts-ignore linkElement.contentDocument.querySelector("#navigation").style = "display: none"; + // @ts-ignore + if (linkElement.contentDocument.querySelector(".content")) { + // If navigation removed, set content margin to 0 to avoid gap. + // @ts-ignore + linkElement.contentDocument.querySelector(".content").style = + "margin-left: 0"; + } + // @ts-ignore linkElement.contentDocument.querySelector("body").style = "font-family: Arial"; From dca61e14596483331e6b18af29b2e26a83b2f126 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Thu, 12 Sep 2024 15:42:08 +0200 Subject: [PATCH 4/7] Close menu on selection (mobile only) --- apps/link/app/(main)/_components/InternalLayout.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/link/app/(main)/_components/InternalLayout.tsx b/apps/link/app/(main)/_components/InternalLayout.tsx index 36062cd..3748592 100644 --- a/apps/link/app/(main)/_components/InternalLayout.tsx +++ b/apps/link/app/(main)/_components/InternalLayout.tsx @@ -24,6 +24,7 @@ import { import { fonts } from "@link-stack/ui"; import Image from "next/image"; import LinkLogo from "public/link-logo-small.png"; +import { usePathname } from 'next/navigation'; interface InternalLayoutProps extends PropsWithChildren { setupModeActive: boolean; @@ -44,6 +45,15 @@ export const InternalLayout: FC = ({ const [openWidth, setOpenWidth] = useState(0); const [closedWidth, setClosedWidth] = useState(0); + const pathname = usePathname(); + + useEffect(() => { + // On mobile, close menu when stuff is selected + if (mobile) { + setOpen(false); + } + }, [pathname]); + useEffect(() => { setClosedWidth(mobile ? 0 : 70); setOpenWidth(270); From 047ef094fcf97033d9623d400e975db6207aa5f5 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Thu, 12 Sep 2024 15:43:48 +0200 Subject: [PATCH 5/7] Hide reporting, admin and profile in sidebar (for mobile) Also, have the "Zammad Interface" link go to mobile version. --- apps/link/app/(main)/_components/Sidebar.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/link/app/(main)/_components/Sidebar.tsx b/apps/link/app/(main)/_components/Sidebar.tsx index 1ef9910..3ef1d7f 100644 --- a/apps/link/app/(main)/_components/Sidebar.tsx +++ b/apps/link/app/(main)/_components/Sidebar.tsx @@ -494,14 +494,14 @@ export const Sidebar: FC = ({ open, setOpen, openWidth, closedWidt selected={pathname.endsWith("/docs")} open={open} /> - + />)} {leafcutterEnabled && ( = ({ open, setOpen, openWidth, closedWidt /> + {!mobile && ( = ({ open, setOpen, openWidth, closedWidt selected={pathname.endsWith("/profile")} open={open} /> - {roles.includes("admin") && ( + )} + {roles.includes("admin") && !mobile && ( <> = ({ open, setOpen, openWidth, closedWidt )} Date: Thu, 12 Sep 2024 15:45:20 +0200 Subject: [PATCH 6/7] Mobile friendly version of ticket view Refactor the header into own component, so it can be shown at "page top" for the mobile one column view. --- .../[id]/@detail/_components/TicketDetail.tsx | 37 ++-------- .../[id]/@edit/_components/TicketEdit.tsx | 2 +- .../app/(main)/tickets/[id]/@header/page.tsx | 12 +++ .../tickets/[id]/_components/TicketHeader.tsx | 73 +++++++++++++++++++ apps/link/app/(main)/tickets/[id]/layout.tsx | 17 ++++- 5 files changed, 107 insertions(+), 34 deletions(-) create mode 100644 apps/link/app/(main)/tickets/[id]/@header/page.tsx create mode 100644 apps/link/app/(main)/tickets/[id]/_components/TicketHeader.tsx diff --git a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx index d24d34a..86b8aab 100644 --- a/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx +++ b/apps/link/app/(main)/tickets/[id]/@detail/_components/TicketDetail.tsx @@ -2,7 +2,7 @@ import { FC, useState, useEffect } from "react"; import { getTicketAction, getTicketArticlesAction } from "app/_actions/tickets"; -import { Grid, Box, Typography } from "@mui/material"; +import { Grid, Box, Typography, useTheme, useMediaQuery } from "@mui/material"; import { Button, fonts, colors } from "@link-stack/ui"; import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css"; import { @@ -13,6 +13,7 @@ import { ConversationHeader, } from "@chatscope/chat-ui-kit-react"; import { ArticleCreateDialog } from "./ArticleCreateDialog"; +import { TicketHeader } from "../../_components/TicketHeader"; interface TicketDetailProps { id: string; @@ -26,6 +27,9 @@ export const TicketDetail: FC = ({ id }) => { const [dialogOpen, setDialogOpen] = useState(false); const [articleKind, setArticleKind] = useState("note"); + const theme = useTheme(); + const mobile = useMediaQuery(theme.breakpoints.down("md")); + useEffect(() => { const fetchTicket = async () => { const result = await getTicketAction(id); @@ -66,36 +70,11 @@ export const TicketDetail: FC = ({ id }) => { <> - + {!mobile && ( - - - {ticket.title} - - {`Ticket #${ticket.number} (created ${new Date( - ticket.createdAt, - ).toLocaleDateString()})`} - + - + )} {ticketArticles.edges.map(({ node: article }: any) => ( = ({ id }) => { const shouldRender = !!ticket; return ( - + {shouldRender && ( diff --git a/apps/link/app/(main)/tickets/[id]/@header/page.tsx b/apps/link/app/(main)/tickets/[id]/@header/page.tsx new file mode 100644 index 0000000..78f8710 --- /dev/null +++ b/apps/link/app/(main)/tickets/[id]/@header/page.tsx @@ -0,0 +1,12 @@ +import { getTicketAction } from "@/app/_actions/tickets"; +import { TicketHeader } from "../_components/TicketHeader"; + +type PageProps = { + params: { + id: string; + }; +}; + +export default function Page({ params: { id } }: PageProps) { + return (); +} diff --git a/apps/link/app/(main)/tickets/[id]/_components/TicketHeader.tsx b/apps/link/app/(main)/tickets/[id]/_components/TicketHeader.tsx new file mode 100644 index 0000000..0f4831e --- /dev/null +++ b/apps/link/app/(main)/tickets/[id]/_components/TicketHeader.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { FC, useState, useEffect } from "react"; +import { Box, Typography } from "@mui/material"; +import { fonts } from "@link-stack/ui"; +import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css"; +import { + ConversationHeader, + ConversationHeaderProps, +} from "@chatscope/chat-ui-kit-react"; +import { getTicketAction } from "@/app/_actions/tickets"; + +interface TicketHeaderProps { + id?: string; + ticket?: any; +} + +export const TicketHeader: FC = ( + props, +) => { + const { poppins, roboto } = fonts; + + const [ticket, setTicket] = useState(props.ticket || null); + + useEffect(() => { + if (!ticket) { + const fetchTicket = async () => { + const result = await getTicketAction(props.id); + setTicket(result); + }; + + fetchTicket(); + + const interval = setInterval(fetchTicket, 20000); + return () => clearInterval(interval); + } + }, [props.id]); + + return ( + <> + + + {ticket?.title} + + + {ticket + ? `Ticket #${ticket.number} (created ${new Date( + ticket.createdAt, + ).toLocaleDateString()})` + : ""} + + + + ); +}; diff --git a/apps/link/app/(main)/tickets/[id]/layout.tsx b/apps/link/app/(main)/tickets/[id]/layout.tsx index d3ba633..9f475c9 100644 --- a/apps/link/app/(main)/tickets/[id]/layout.tsx +++ b/apps/link/app/(main)/tickets/[id]/layout.tsx @@ -1,22 +1,31 @@ "use client"; +import { ConversationHeader } from "@chatscope/chat-ui-kit-react"; import { Grid } from "@mui/material"; type LayoutProps = { detail: any; edit: any; + header: any; params: { id: string; }; }; -export default function Layout({ detail, edit, params: { id } }: LayoutProps) { +export default function Layout({ detail, edit, header, params: { id } }: LayoutProps) { return ( - - + + + + + {header} + + + + {detail} - + {edit} From 8c7e19bae8375b5c6447f245d4c235ffbcc9adf6 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Thu, 12 Sep 2024 15:56:57 +0200 Subject: [PATCH 7/7] Hide some columns in ticket list (on mobile) --- .../overview/[overview]/_components/TicketList.tsx | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx b/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx index cb45eab..7c140d3 100644 --- a/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx +++ b/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx @@ -1,7 +1,7 @@ "use client"; import { FC, useState } from "react"; -import { Grid, Box } from "@mui/material"; +import { Grid, Box, useTheme, useMediaQuery } from "@mui/material"; import { GridColDef } from "@mui/x-data-grid-pro"; import { StyledDataGrid } from "app/(main)/_components/StyledDataGrid"; import { Button, List, typography } from "@link-stack/ui"; @@ -16,6 +16,10 @@ interface TicketListProps { export const TicketList: FC = ({ title, tickets }) => { const [dialogOpen, setDialogOpen] = useState(false); const router = useRouter(); + + const theme = useTheme(); + const mobile = useMediaQuery(theme.breakpoints.down("sm")); + let gridColumns: GridColDef[] = [ { field: "number", @@ -32,7 +36,9 @@ export const TicketList: FC = ({ title, tickets }) => { headerName: "Sender", valueGetter: (value: any) => value?.fullname, flex: 1, - }, + }]; + if (!mobile) { + gridColumns.push( { field: "createdAt", headerName: "Created At", @@ -50,8 +56,8 @@ export const TicketList: FC = ({ title, tickets }) => { headerName: "Group", valueGetter: (value: any) => value?.name, flex: 1, - }, - ]; + }); + } const onRowClick = (id: any) => { router.push(`/tickets/${id}`);