diff --git a/apps/leafcutter/package.json b/apps/leafcutter/package.json index e1997ed..be341ec 100644 --- a/apps/leafcutter/package.json +++ b/apps/leafcutter/package.json @@ -17,24 +17,24 @@ "@emotion/react": "^11.11.1", "@emotion/server": "^11.11.0", "@emotion/styled": "^11.11.0", - "@fontsource/playfair-display": "^5.0.13", + "@fontsource/playfair-display": "^5.0.15", "@fontsource/poppins": "^5.0.8", "@fontsource/roboto": "^5.0.8", "@mui/icons-material": "^5", - "@mui/lab": "^5.0.0-alpha.146", + "@mui/lab": "^5.0.0-alpha.148", "@mui/material": "^5", - "@mui/x-data-grid-pro": "^6.16.0", - "@mui/x-date-pickers-pro": "^6.16.0", - "@opensearch-project/opensearch": "^2.3.1", + "@mui/x-data-grid-pro": "^6.16.2", + "@mui/x-date-pickers-pro": "^6.16.2", + "@opensearch-project/opensearch": "^2.4.0", "cryptr": "^6.3.0", "date-fns": "^2.30.0", "http-proxy-middleware": "^2.0.6", "leafcutter-common": "*", "material-ui-popup-state": "^5.0.9", - "next": "13.5.3", - "next-auth": "^4.23.1", - "next-http-proxy-middleware": "^1.2.5", - "nodemailer": "^6.9.5", + "next": "13.5.4", + "next-auth": "^4.23.2", + "next-http-proxy-middleware": "^1.2.6", + "nodemailer": "^6.9.6", "react": "18.2.0", "react-cookie": "^6.1.1", "react-cookie-consent": "^8.0.1", @@ -48,18 +48,18 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@babel/core": "^7.23.0", - "@types/node": "^20.8.0", - "@types/react": "18.2.24", - "@types/uuid": "^9.0.4", + "@babel/core": "^7.23.2", + "@types/node": "^20.8.6", + "@types/react": "18.2.28", + "@types/uuid": "^9.0.5", "babel-loader": "^9.1.3", - "eslint": "^8.50.0", + "eslint": "^8.51.0", "eslint-config-airbnb": "^19.0.4", - "eslint-config-next": "^13.5.3", + "eslint-config-next": "^13.5.4", "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.28.1", "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-react": "^7.33.2", "typescript": "5.2.2" } diff --git a/apps/link/app/(main)/_components/SearchBox.tsx b/apps/link/app/(main)/_components/SearchBox.tsx new file mode 100644 index 0000000..e36361b --- /dev/null +++ b/apps/link/app/(main)/_components/SearchBox.tsx @@ -0,0 +1,150 @@ +import { FC, useState, useEffect } from "react"; +import { usePathname, useRouter } from "next/navigation"; +import useSWR from "swr"; +import { Grid, Box, TextField, Autocomplete } from "@mui/material"; +import { searchQuery } from "@/app/_graphql/searchQuery"; +import { colors } from "@/app/_styles/theme"; + +type SearchResultProps = { + props: any; + option: any; +}; + +const SearchInput = (params: any) => ( + +); + +const SearchResult: FC = ({ props, option }) => { + console.log({ option }); + + const { lightGrey, mediumGray, black, white } = colors; + + return ( + + + + + + {option.title} + + + + + + + {option.note} + + + + + + ); +}; + +export const SearchBox: FC = () => { + const [open, setOpen] = useState(false); + const [selectedValue, setSelectedValue] = useState(null); + const [searchTerms, setSearchTerms] = useState(null); + const pathname = usePathname(); + const router = useRouter(); + const { data, error }: any = useSWR({ + document: searchQuery, + variables: { + search: searchTerms ?? "", + limit: 50, + }, + refreshInterval: 10000, + }); + + useEffect(() => { + setOpen(false); + }, [pathname]); + + return ( + setOpen(false)} + inputValue={searchTerms} + onChange={(_event, option, reason) => { + if (!option) return; + const url = `/tickets/${option.internalId}`; + setSelectedValue(""); + router.push(url); + }} + onInputChange={(_event, value) => { + setSearchTerms(value); + }} + open={open} + onOpen={() => setOpen(true)} + noOptionsText="No results" + options={data?.search ?? []} + getOptionLabel={(option: any) => { + if (option) { + return option.title; + } else { + return ""; + } + }} + renderOption={(props, option: any) => ( + + )} + sx={{ width: "100%" }} + renderInput={(params: any) => } + /> + ); +}; diff --git a/apps/link/app/(main)/_components/Sidebar.tsx b/apps/link/app/(main)/_components/Sidebar.tsx index ad053f2..cacd4fd 100644 --- a/apps/link/app/(main)/_components/Sidebar.tsx +++ b/apps/link/app/(main)/_components/Sidebar.tsx @@ -1,6 +1,6 @@ "use client"; -import { FC, useState } from "react"; +import { FC, useEffect, useState } from "react"; import useSWR from "swr"; import { Box, @@ -26,6 +26,7 @@ import { Assessment as AssessmentIcon, LibraryBooks as LibraryBooksIcon, School as SchoolIcon, + Search as SearchIcon, } from "@mui/icons-material"; import { usePathname } from "next/navigation"; import Link from "next/link"; @@ -33,8 +34,8 @@ 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"; +import { SearchBox } from "./SearchBox"; -console.log; const openWidth = 270; const closedWidth = 100; @@ -174,13 +175,15 @@ export const Sidebar: FC = ({ open, setOpen }) => { }, { refreshInterval: 10000 }, ); + console.log({ overviewData }); const findOverviewCountByID = (id: number) => overviewData?.ticketOverviews?.edges?.find((overview: any) => overview.node.id.endsWith(`/${id}`), )?.node?.ticketCount ?? 0; + const recentCount = 0; const assignedCount = findOverviewCountByID(1); + const openCount = findOverviewCountByID(5); const urgentCount = findOverviewCountByID(7); - const pendingCount = findOverviewCountByID(3); const unassignedCount = findOverviewCountByID(2); const logout = () => { @@ -227,6 +230,7 @@ export const Sidebar: FC = ({ open, setOpen }) => { direction="column" justifyContent="space-between" wrap="nowrap" + spacing={0} sx={{ backgroundColor: "#25272A", height: "100%", p: 2 }} > @@ -313,9 +317,17 @@ export const Sidebar: FC = ({ open, setOpen }) => { + + + = ({ open, setOpen }) => { /> = ({ open, setOpen }) => { > + = ({ open, setOpen }) => { open={open} /> = ({ hideSidebar = true, }) => { const router = useRouter(); - const { data: session } = useSession(); + const { data: session } = useSession({ required: true }); + const timeoutRef = useRef(null); const [authenticated, setAuthenticated] = useState(false); const [display, setDisplay] = useState("none"); const url = `/zammad${path}`; @@ -28,7 +29,7 @@ export const ZammadWrapper: FC = ({ method: "GET", redirect: "manual", }); - + console.log({ res }); if (res.type === "opaqueredirect") { setAuthenticated(true); } else { @@ -39,8 +40,24 @@ export const ZammadWrapper: FC = ({ checkAuthenticated(); }, [path]); - if (!session) { - console.log("No session"); + useEffect(() => { + if (session === null) { + timeoutRef.current = setTimeout(() => { + if (session === null) { + router.push("/login"); + } + }, 3000); + } + + if (session !== null) { + clearTimeout(timeoutRef.current); + } + + return () => clearTimeout(timeoutRef.current); + }, [session]); + + if (!session || !authenticated) { + console.log("Not authenticated"); return ( void; +} + +export const TicketCreateDialog: FC = ({ + open, + closeDialog, +}) => { + const [kind, setKind] = useState("note"); + const [customerID, setCustomerID] = useState(""); + const [groupID, setGroupID] = useState(""); + const [ownerID, setOwnerID] = useState(""); + const [priorityID, setPriorityID] = useState(""); + const [stateID, setStateID] = useState(""); + const [tags, setTags] = useState([]); + const [title, setTitle] = useState(""); + const [body, setBody] = useState(""); + const backgroundColor = kind === "note" ? "#FFB620" : "#1982FC"; + const color = kind === "note" ? "black" : "white"; + const { fetcher } = useSWRConfig(); + const ticket = { + customerId: customerID, + groupId: groupID, + ownerId: ownerID, + priorityId: priorityID, + stateId: stateID, + tags, + title, + article: { + body, + type: kind, + internal: kind === "note", + } + }; + + + const createTicket = async () => { + await fetcher({ + document: createTicketMutation, + variables: { + input: { + ticket + }, + }, + }); + closeDialog(); + setBody(""); + }; + + return ( + + + + + setTitle(e.target.value)} + /> + + + setCustomerID(e.target.value)} + renderInput={(params) => } + /> + + + setBody(e.target.value)} + /> + + + + + + + + + + + + + + + ); +}; diff --git a/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx b/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx index 3a2e487..317edcc 100644 --- a/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx +++ b/apps/link/app/(main)/overview/[overview]/_components/TicketList.tsx @@ -1,12 +1,13 @@ "use client"; -import { FC } from "react"; +import { FC, useState } from "react"; import { Grid, Box } from "@mui/material"; import { GridColDef } from "@mui/x-data-grid-pro"; import { StyledDataGrid } from "../../../_components/StyledDataGrid"; import { Button } from "../../../../_components/Button"; import { typography } from "../../../../_styles/theme"; import { useRouter } from "next/navigation"; +import { TicketCreateDialog } from "./TicketCreateDialog"; interface TicketListProps { title: string; @@ -14,72 +15,96 @@ interface TicketListProps { } export const TicketList: FC = ({ title, tickets }) => { + const [dialogOpen, setDialogOpen] = useState(false); const router = useRouter(); let gridColumns: GridColDef[] = [ { field: "number", headerName: "Number", - flex: 0.3, + flex: 1, }, { field: "title", headerName: "Title", - flex: 1.5, + flex: 5, }, { field: "customer", headerName: "Sender", valueGetter: (params) => params.row?.customer?.fullname, - flex: 0.6, + flex: 2, + }, + { + field: "createdAt", + headerName: "Created At", + valueGetter: (params) => new Date(params.row?.createdAt).toLocaleString(), + flex: 1, + }, + { + field: "updatedAt", + headerName: "Updated At", + valueGetter: (params) => new Date(params.row?.updatedAt).toLocaleString(), + flex: 1, }, { field: "group", headerName: "Group", valueGetter: (params) => params.row?.group?.name, - flex: 0.3, + flex: 1, }, ]; - console.log({ tickets }); + const rowClick = ({ row }) => { router.push(`/tickets/${row.internalId}`); }; return ( - - - - - - {title} - + <> + + + + + + {title} + + + +