diff --git a/apps/link/app/(main)/_components/InternalLayout.tsx b/apps/link/app/(main)/_components/InternalLayout.tsx index e80b52a..3748592 100644 --- a/apps/link/app/(main)/_components/InternalLayout.tsx +++ b/apps/link/app/(main)/_components/InternalLayout.tsx @@ -1,9 +1,30 @@ "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"; +import { usePathname } from 'next/navigation'; interface InternalLayoutProps extends PropsWithChildren { setupModeActive: boolean; @@ -15,20 +36,102 @@ 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); + + const pathname = usePathname(); + + useEffect(() => { + // On mobile, close menu when stuff is selected + if (mobile) { + setOpen(false); + } + }, [pathname]); + + 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..3ef1d7f 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: openWidth == 0 ? "hidden" : "visible", }, }} > @@ -493,14 +494,14 @@ export const Sidebar: FC = ({ selected={pathname.endsWith("/docs")} open={open} /> - + />)} {leafcutterEnabled && ( = ({ /> + {!mobile && ( = ({ selected={pathname.endsWith("/profile")} open={open} /> - {roles.includes("admin") && ( + )} + {roles.includes("admin") && !mobile && ( <> = ({ )} = ({ // @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"; 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}`); 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 35ce25b..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} 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} - +