Compare commits
7 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c7e19bae8 | ||
|
|
6440c402cf | ||
|
|
047ef094fc | ||
|
|
dca61e1459 | ||
|
|
89fdd955fe | ||
|
|
566d3643de | ||
|
|
b621014178 |
10 changed files with 254 additions and 62 deletions
|
|
@ -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<InternalLayoutProps> = ({
|
|||
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<number>(0);
|
||||
const [closedWidth, setClosedWidth] = useState<number>(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",
|
||||
})<HeaderBarProps>(({ theme, open }) => ({
|
||||
backgroundColor: "#25272A",
|
||||
width: "100%",
|
||||
height: "56px",
|
||||
}));
|
||||
|
||||
return (
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<SetupModeWarning setupModeActive={setupModeActive} />
|
||||
<Grid container direction="row">
|
||||
{mobile ? (<MuiAppBar>
|
||||
<HeaderBar>
|
||||
<IconButton
|
||||
sx={{ color: "white" }}
|
||||
aria-label="open drawer"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<MenuSharp />
|
||||
</IconButton>
|
||||
<Typography
|
||||
variant="h2"
|
||||
sx={{
|
||||
fontSize: 26,
|
||||
color: "white",
|
||||
fontWeight: 700,
|
||||
fontFamily: poppins.style.fontFamily,
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
CDR Link
|
||||
</Typography>
|
||||
<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>
|
||||
</HeaderBar>
|
||||
</MuiAppBar>) : (null)}
|
||||
<Sidebar
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
leafcutterEnabled={leafcutterEnabled}
|
||||
openWidth={openWidth}
|
||||
closedWidth={closedWidth}
|
||||
/>
|
||||
<Grid
|
||||
item
|
||||
sx={{ ml: open ? "270px" : "70px", width: "100%", height: "100vh" }}
|
||||
sx={{
|
||||
mt: { xs: "56px", sm: "0" },
|
||||
ml: { xs: open ? "270px" : "0px", sm: open ? "270px" : "70px" },
|
||||
width: "100%",
|
||||
height: { xs: "calc(100vh - 56px)", sm: "100vh" },
|
||||
position: "relative",
|
||||
}}
|
||||
>
|
||||
{children as any}
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -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<SidebarProps> = ({
|
||||
open,
|
||||
setOpen,
|
||||
leafcutterEnabled = false,
|
||||
}) => {
|
||||
export const Sidebar: FC<SidebarProps> = ({ open, setOpen, openWidth, closedWidth, leafcutterEnabled = false }) => {
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
const [overviewCounts, setOverviewCounts] = useState<any>(null);
|
||||
|
|
@ -207,6 +204,9 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
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<SidebarProps> = ({
|
|||
return (
|
||||
<Drawer
|
||||
sx={{ width: open ? openWidth : closedWidth, flexShrink: 0 }}
|
||||
variant="permanent"
|
||||
variant={mobile ? "temporary" : "permanent"}
|
||||
anchor="left"
|
||||
open={open}
|
||||
onClose={() => {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<SidebarProps> = ({
|
|||
selected={pathname.endsWith("/docs")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
{!mobile && (<MenuItem
|
||||
name="Reporting"
|
||||
href="/reporting"
|
||||
Icon={AssessmentIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/reporting")}
|
||||
open={open}
|
||||
/>
|
||||
/>)}
|
||||
{leafcutterEnabled && (
|
||||
<MenuItem
|
||||
name="Leafcutter"
|
||||
|
|
@ -557,6 +558,7 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
/>
|
||||
</List>
|
||||
</Collapse>
|
||||
{!mobile && (
|
||||
<MenuItem
|
||||
name="Profile"
|
||||
href="/profile"
|
||||
|
|
@ -565,7 +567,8 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
selected={pathname.endsWith("/profile")}
|
||||
open={open}
|
||||
/>
|
||||
{roles.includes("admin") && (
|
||||
)}
|
||||
{roles.includes("admin") && !mobile && (
|
||||
<>
|
||||
<MenuItem
|
||||
name="Admin"
|
||||
|
|
@ -655,7 +658,7 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
)}
|
||||
<MenuItem
|
||||
name="Zammad Interface"
|
||||
href="/zammad"
|
||||
href={mobile ? "/mobile" : "/zammad"}
|
||||
Icon={DvrIcon}
|
||||
iconSize={20}
|
||||
open={open}
|
||||
|
|
|
|||
|
|
@ -115,6 +115,14 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
// @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";
|
||||
|
|
|
|||
|
|
@ -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<TicketListProps> = ({ 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<TicketListProps> = ({ 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<TicketListProps> = ({ title, tickets }) => {
|
|||
headerName: "Group",
|
||||
valueGetter: (value: any) => value?.name,
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
const onRowClick = (id: any) => {
|
||||
router.push(`/tickets/${id}`);
|
||||
|
|
|
|||
|
|
@ -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<TicketDetailProps> = ({ 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<TicketDetailProps> = ({ id }) => {
|
|||
<>
|
||||
<MainContainer>
|
||||
<ChatContainer>
|
||||
<ConversationHeader>
|
||||
{!mobile && (<ConversationHeader>
|
||||
<ConversationHeader.Content>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontFamily: poppins.style.fontFamily,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{ticket.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>{`Ticket #${ticket.number} (created ${new Date(
|
||||
ticket.createdAt,
|
||||
).toLocaleDateString()})`}</Typography>
|
||||
</Box>
|
||||
<TicketHeader ticket={ticket} />
|
||||
</ConversationHeader.Content>
|
||||
</ConversationHeader>
|
||||
</ConversationHeader>)}
|
||||
<MessageList style={{ marginBottom: 80 }}>
|
||||
{ticketArticles.edges.map(({ node: article }: any) => (
|
||||
<Message
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
const shouldRender = !!ticket;
|
||||
|
||||
return (
|
||||
<Box sx={{ height: "100vh", background: "#ddd", p: 2 }}>
|
||||
<Box sx={{ background: "#ddd", p: 2 }}>
|
||||
{shouldRender && (
|
||||
<Grid container direction="column" spacing={3}>
|
||||
<Grid item>
|
||||
|
|
|
|||
12
apps/link/app/(main)/tickets/[id]/@header/page.tsx
Normal file
12
apps/link/app/(main)/tickets/[id]/@header/page.tsx
Normal file
|
|
@ -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 (<TicketHeader id={id} />);
|
||||
}
|
||||
|
|
@ -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<ConversationHeaderProps & TicketHeaderProps> = (
|
||||
props,
|
||||
) => {
|
||||
const { poppins, roboto } = fonts;
|
||||
|
||||
const [ticket, setTicket] = useState<any>(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 (
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
textAlign: "center",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
fontFamily: poppins.style.fontFamily,
|
||||
fontWeight: 700,
|
||||
}}
|
||||
>
|
||||
{ticket?.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{
|
||||
fontFamily: roboto.style.fontFamily,
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{ticket
|
||||
? `Ticket #${ticket.number} (created ${new Date(
|
||||
ticket.createdAt,
|
||||
).toLocaleDateString()})`
|
||||
: ""}
|
||||
</Typography>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<Grid container spacing={0} sx={{ height: "100vh" }} direction="row">
|
||||
<Grid item sx={{ height: "100vh" }} xs={9}>
|
||||
<Grid container spacing={0} sx={{ height: "100%" }} direction="row" wrap="wrap">
|
||||
<Grid item sx={{ order: 0, display: { xs: "block", md: "none" } }} xs={12}>
|
||||
<ConversationHeader>
|
||||
<ConversationHeader.Content>
|
||||
{header}
|
||||
</ConversationHeader.Content>
|
||||
</ConversationHeader>
|
||||
</Grid>
|
||||
<Grid item sx={{ height: {xs: "auto", md: "100%"}, order: {xs: 3, md: 1} }} xs={12} md={9}>
|
||||
{detail}
|
||||
</Grid>
|
||||
<Grid item xs={3} sx={{ height: "100vh" }}>
|
||||
<Grid item xs={12} md={3} sx={{ height: {xs: "auto", md: "100%"}, order: 2 }}>
|
||||
{edit}
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -36,8 +36,8 @@ export const List: FC<ListProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ height: "100vh", backgroundColor: lightGray, p: 3 }}>
|
||||
<Grid container direction="column">
|
||||
<Box sx={{ height: "100%", p: 3 }}>
|
||||
<Grid container direction="column" sx={{ height: "100%", flexWrap: "nowrap" }}>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
|
|
@ -52,14 +52,13 @@ export const List: FC<ListProps> = ({
|
|||
{buttons}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid item sx={{flexGrow: 1}}>
|
||||
<Box
|
||||
sx={{
|
||||
mt: 2,
|
||||
backgroundColor: "transparent",
|
||||
border: 0,
|
||||
width: "100%",
|
||||
height: "calc(100vh - 100px)",
|
||||
".MuiDataGrid-row": {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue