Remove leafcutter and related packages
This commit is contained in:
parent
48165db6a2
commit
3a1063e40e
264 changed files with 1763 additions and 25062 deletions
13
apps/link/app/(main)/_components/DefaultDashboard.tsx
Normal file
13
apps/link/app/(main)/_components/DefaultDashboard.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { OpenSearchWrapper } from "@/app/_components/OpenSearchWrapper";
|
||||
|
||||
export function DefaultDashboard() {
|
||||
// Extract just the URL path from the full dashboard URL
|
||||
// The env var format is like: app/dashboards?security_tenant=global#/view/...
|
||||
const defaultUrl =
|
||||
process.env.NEXT_PUBLIC_OPENSEARCH_DEFAULT_DASHBOARD_URL ||
|
||||
"app/dashboards#/";
|
||||
|
||||
return <OpenSearchWrapper url={defaultUrl} />;
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { OpenSearchWrapper } from "@link-stack/leafcutter-ui";
|
||||
|
||||
type HomeProps = {
|
||||
url: string;
|
||||
};
|
||||
|
||||
export const Home: FC<HomeProps> = ({ url }) => (
|
||||
<OpenSearchWrapper url={url} margin={0} />
|
||||
);
|
||||
|
|
@ -31,8 +31,6 @@ import Link from "next/link";
|
|||
import Image from "next/image";
|
||||
import LinkLogo from "@app/../public/link-logo-small.png";
|
||||
import { useSession, signOut } from "next-auth/react";
|
||||
import { getOverviewTicketCountsAction } from "@/app/_actions/overviews";
|
||||
import { SearchBox } from "./SearchBox";
|
||||
import { fonts } from "@link-stack/ui";
|
||||
|
||||
const openWidth = 270;
|
||||
|
|
@ -188,25 +186,11 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
}) => {
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
const [overviewCounts, setOverviewCounts] = useState<any>(null);
|
||||
const { poppins } = fonts;
|
||||
const username = session?.user?.name || "";
|
||||
// @ts-ignore
|
||||
const roles = session?.user?.roles || [];
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCounts = async () => {
|
||||
const counts = await getOverviewTicketCountsAction();
|
||||
setOverviewCounts(counts);
|
||||
};
|
||||
|
||||
fetchCounts();
|
||||
|
||||
const interval = setInterval(fetchCounts, 30000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
sx={{ width: open ? openWidth : closedWidth, flexShrink: 0 }}
|
||||
|
|
@ -342,7 +326,6 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>{open && <SearchBox />}</Grid>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
|
|
@ -374,9 +357,6 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
".badge": {
|
||||
p: { fontSize: 12, color: "black !important" },
|
||||
},
|
||||
},
|
||||
".Mui-selected": {
|
||||
background: "#444",
|
||||
|
|
@ -387,92 +367,18 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
fontSize: 16,
|
||||
},
|
||||
},
|
||||
".badge": {
|
||||
p: { fontSize: 12, color: "black !important" },
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{leafcutterEnabled && (
|
||||
<MenuItem
|
||||
name="Home"
|
||||
href="/"
|
||||
Icon={CottageIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/")}
|
||||
open={open}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
name="Tickets"
|
||||
href="/overview/recent"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
selected={
|
||||
pathname.startsWith("/overview") ||
|
||||
pathname.startsWith("/tickets")
|
||||
}
|
||||
name="Dashboards"
|
||||
href="/dashboards"
|
||||
Icon={InsightsIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.startsWith("/dashboards")}
|
||||
open={open}
|
||||
/>
|
||||
<Collapse
|
||||
in={
|
||||
open &&
|
||||
(pathname.startsWith("/overview") ||
|
||||
pathname.startsWith("/tickets"))
|
||||
}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
onClick={undefined}
|
||||
>
|
||||
<List component="div" disablePadding>
|
||||
<MenuItem
|
||||
name="Recent"
|
||||
href="/overview/recent"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/overview/recent")}
|
||||
badge={overviewCounts?.recent}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Open"
|
||||
href="/overview/open"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/overview/open")}
|
||||
badge={overviewCounts?.open}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Urgent"
|
||||
href="/overview/urgent"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/overview/urgent")}
|
||||
badge={overviewCounts?.urgent}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Assigned"
|
||||
href="/overview/assigned"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/overview/assigned")}
|
||||
badge={overviewCounts?.assigned}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Unassigned"
|
||||
href="/overview/unassigned"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/overview/unassigned")}
|
||||
badge={overviewCounts?.unassigned}
|
||||
open={open}
|
||||
/>
|
||||
</List>
|
||||
</Collapse>
|
||||
<MenuItem
|
||||
name="Documentation"
|
||||
href="/docs"
|
||||
|
|
@ -481,72 +387,6 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
selected={pathname.endsWith("/docs")}
|
||||
open={open}
|
||||
/>
|
||||
{roles.includes("admin") && leafcutterEnabled && (
|
||||
<MenuItem
|
||||
name="Opensearch"
|
||||
href="/opensearch"
|
||||
Icon={InsightsIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.startsWith("/opensearch")}
|
||||
open={open}
|
||||
/>
|
||||
)}
|
||||
{false && leafcutterEnabled && (
|
||||
<MenuItem
|
||||
name="Leafcutter"
|
||||
href="/leafcutter"
|
||||
Icon={InsightsIcon}
|
||||
iconSize={20}
|
||||
selected={false}
|
||||
open={open}
|
||||
/>
|
||||
)}
|
||||
<Collapse
|
||||
in={open && 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={InsightsIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/leafcutter/about")}
|
||||
open={open}
|
||||
/>
|
||||
</List>
|
||||
</Collapse>
|
||||
{roles.includes("admin") && (
|
||||
<>
|
||||
<MenuItem
|
||||
|
|
@ -555,6 +395,7 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
Icon={SettingsIcon}
|
||||
iconSize={20}
|
||||
open={open}
|
||||
selected={pathname.startsWith("/admin")}
|
||||
/>
|
||||
<Collapse
|
||||
in={open && pathname.startsWith("/admin/")}
|
||||
|
|
@ -564,55 +405,47 @@ export const Sidebar: FC<SidebarProps> = ({
|
|||
>
|
||||
<List component="div" disablePadding>
|
||||
<MenuItem
|
||||
name="CDR Bridge"
|
||||
href="/admin/bridge"
|
||||
selected={pathname.endsWith("/admin/bridge")}
|
||||
name="WhatsApp"
|
||||
href="/admin/bridge/whatsapp"
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/bridge/whatsapp")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Signal"
|
||||
href="/admin/bridge/signal"
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/bridge/signal")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Facebook"
|
||||
href="/admin/bridge/facebook"
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/bridge/facebook")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Voice"
|
||||
href="/admin/bridge/voice"
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/bridge/voice")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Webhooks"
|
||||
href="/admin/bridge/webhooks"
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/bridge/webhooks")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="OpenSearch"
|
||||
href="/admin/opensearch"
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/opensearch")}
|
||||
open={open}
|
||||
/>
|
||||
<Collapse
|
||||
in={open && pathname.startsWith("/admin/bridge")}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
onClick={undefined}
|
||||
>
|
||||
<List component="div" disablePadding>
|
||||
<MenuItem
|
||||
name="WhatsApp"
|
||||
href="/admin/bridge/whatsapp"
|
||||
depth={1}
|
||||
selected={pathname.endsWith("/admin/bridge/whatsapp")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Signal"
|
||||
href="/admin/bridge/signal"
|
||||
depth={1}
|
||||
selected={pathname.endsWith("/admin/bridge/signal")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Facebook"
|
||||
href="/admin/bridge/facebook"
|
||||
depth={1}
|
||||
selected={pathname.endsWith("/admin/bridge/facebook")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Voice"
|
||||
href="/admin/bridge/voice"
|
||||
depth={1}
|
||||
selected={pathname.endsWith("/admin/bridge/voice")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Webhooks"
|
||||
href="/admin/bridge/webhooks"
|
||||
depth={1}
|
||||
selected={pathname.endsWith("/admin/bridge/webhooks")}
|
||||
open={open}
|
||||
/>
|
||||
</List>
|
||||
</Collapse>
|
||||
</List>
|
||||
</Collapse>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,24 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Grid } from "@mui/material";
|
||||
import Iframe from "react-iframe";
|
||||
|
||||
export const LabelStudioWrapper: FC = () => (
|
||||
<Grid
|
||||
container
|
||||
spacing={0}
|
||||
sx={{ height: "100%", width: "100%" }}
|
||||
direction="column"
|
||||
>
|
||||
<Grid item sx={{ height: "100vh", width: "100%" }}>
|
||||
<Iframe
|
||||
id="label-studio"
|
||||
url={"/label-studio"}
|
||||
width="100%"
|
||||
height="100%"
|
||||
frameBorder={0}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Metadata } from "next";
|
||||
import { LabelStudioWrapper } from "./_components/LabelStudioWrapper";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Label Studio",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <LabelStudioWrapper />;
|
||||
}
|
||||
10
apps/link/app/(main)/admin/opensearch/page.tsx
Normal file
10
apps/link/app/(main)/admin/opensearch/page.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { Metadata } from "next";
|
||||
import { OpenSearchWrapper } from "@/app/_components/OpenSearchWrapper";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "CDR Link - OpenSearch",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <OpenSearchWrapper url="app/home#/" />;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Metadata } from "next";
|
||||
import { ZammadWrapper } from "app/(main)/_components/ZammadWrapper";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Zammad",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <ZammadWrapper path="/#manage" hideSidebar={false} />;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { About } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export default function Page() {
|
||||
return <About />;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { getTemplates } from "@link-stack/opensearch-common";
|
||||
import { Create } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function Page() {
|
||||
const templates = await getTemplates(100);
|
||||
|
||||
return <Create templates={templates} />;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { FAQ } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export default function Page() {
|
||||
return <FAQ />;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { ReactNode } from "react";
|
||||
import { LeafcutterWrapper } from "@link-stack/leafcutter-ui";
|
||||
|
||||
type LayoutProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
return <LeafcutterWrapper>{children}</LeafcutterWrapper>;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { Home, LeafcutterWrapper } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<LeafcutterWrapper>
|
||||
<Home visualizations={[]} showWelcome={false} />
|
||||
</LeafcutterWrapper>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Trends } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function Page() {
|
||||
return <Trends visualizations={[]} />;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import { OpenSearchWrapper } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export default function Page() {
|
||||
return <OpenSearchWrapper url="/app/visualize#/" margin={50} />;
|
||||
}
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState, useEffect, useActionState } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Grid } from "@mui/material";
|
||||
import {
|
||||
Dialog,
|
||||
Button,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
Select,
|
||||
} from "@link-stack/ui";
|
||||
import { createTicketAction } from "app/_actions/tickets";
|
||||
import { getCustomersAction } from "app/_actions/users";
|
||||
import { getGroupsAction } from "app/_actions/groups";
|
||||
|
||||
interface TicketCreateDialogProps {
|
||||
open: boolean;
|
||||
closeDialog: () => void;
|
||||
}
|
||||
|
||||
export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
|
||||
open,
|
||||
closeDialog,
|
||||
}) => {
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const initialState = {
|
||||
messages: [],
|
||||
errors: [],
|
||||
values: {
|
||||
customerId: "",
|
||||
groupId: "",
|
||||
ownerId: "",
|
||||
priorityId: "",
|
||||
stateId: "",
|
||||
tags: [],
|
||||
title: "",
|
||||
article: {
|
||||
body: "",
|
||||
type: "note",
|
||||
internal: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
const [formState, formAction] = useActionState(
|
||||
createTicketAction,
|
||||
initialState,
|
||||
);
|
||||
const [liveFormState, setLiveFormState] = useState(formState);
|
||||
const updateFormState = (field: string, value: any) => {
|
||||
const newState = { ...liveFormState };
|
||||
newState.values[field] = value;
|
||||
setLiveFormState(newState);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUsers = async () => {
|
||||
const result = await getCustomersAction();
|
||||
setCustomers(result);
|
||||
};
|
||||
|
||||
fetchUsers();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGroups = async () => {
|
||||
const result = await getGroupsAction();
|
||||
setGroups(result);
|
||||
};
|
||||
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (formState.success) {
|
||||
closeDialog();
|
||||
}
|
||||
}, [formState.success, router]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title="Create Ticket"
|
||||
open={open}
|
||||
onClose={closeDialog}
|
||||
formAction={formAction}
|
||||
buttons={
|
||||
<Grid container justifyContent="space-between">
|
||||
<Grid item>
|
||||
<Button
|
||||
text="Cancel"
|
||||
kind="secondary"
|
||||
onClick={() => {
|
||||
closeDialog();
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button text="Save" type="submit" kind="primary" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
>
|
||||
<Grid container direction="column" spacing={3}>
|
||||
<Grid item>
|
||||
<Select
|
||||
name="groupId"
|
||||
label="Group"
|
||||
getOptions={() => groups as any}
|
||||
formState={liveFormState}
|
||||
updateFormState={updateFormState}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Select
|
||||
name="customerId"
|
||||
label="Customer"
|
||||
getOptions={() => customers as any}
|
||||
formState={liveFormState}
|
||||
updateFormState={updateFormState}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField name="title" label="Title" formState={formState} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<TextField
|
||||
name="details"
|
||||
label="Details"
|
||||
lines={10}
|
||||
formState={formState}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { Grid, Box } 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";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { TicketCreateDialog } from "./TicketCreateDialog";
|
||||
|
||||
interface TicketListProps {
|
||||
title: string;
|
||||
tickets: any;
|
||||
}
|
||||
|
||||
export const TicketList: FC<TicketListProps> = ({ title, tickets }) => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
let gridColumns: GridColDef[] = [
|
||||
{
|
||||
field: "number",
|
||||
headerName: "Number",
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: "title",
|
||||
headerName: "Title",
|
||||
flex: 3,
|
||||
},
|
||||
{
|
||||
field: "customer",
|
||||
headerName: "Sender",
|
||||
valueGetter: (value: any) => value?.fullname,
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: "createdAt",
|
||||
headerName: "Created At",
|
||||
valueGetter: (value: any) => new Date(value).toLocaleString(),
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: "updatedAt",
|
||||
headerName: "Updated At",
|
||||
valueGetter: (value: any) => new Date(value).toLocaleString(),
|
||||
flex: 1,
|
||||
},
|
||||
{
|
||||
field: "group",
|
||||
headerName: "Group",
|
||||
valueGetter: (value: any) => value?.name,
|
||||
flex: 1,
|
||||
},
|
||||
];
|
||||
|
||||
const onRowClick = (id: any) => {
|
||||
router.push(`/tickets/${id}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<List
|
||||
title={title}
|
||||
rows={tickets}
|
||||
columns={gridColumns}
|
||||
onRowClick={onRowClick}
|
||||
getRowID={(row: any) => {
|
||||
return row.internalId;
|
||||
}}
|
||||
buttons={
|
||||
<Grid container direction="row-reverse" alignItems="center">
|
||||
<Grid item>
|
||||
<Button
|
||||
onClick={() => setDialogOpen(true)}
|
||||
text="Create"
|
||||
color="primary"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
}
|
||||
/>
|
||||
<TicketCreateDialog
|
||||
open={dialogOpen}
|
||||
closeDialog={() => setDialogOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getOverviewTicketsAction } from "app/_actions/overviews";
|
||||
|
||||
import { TicketList } from "./TicketList";
|
||||
|
||||
type ZammadOverviewProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
|
||||
const [tickets, setTickets] = useState([]);
|
||||
|
||||
if (typeof window !== "undefined") {
|
||||
useEffect(() => {
|
||||
const hash = window?.location?.hash;
|
||||
|
||||
if (hash) {
|
||||
const ticketID = hash.replace("#ticket/zoom/", "");
|
||||
if (ticketID && !isNaN(parseInt(ticketID, 10))) {
|
||||
redirect(`/tickets/${ticketID}`);
|
||||
}
|
||||
}
|
||||
}, [window?.location?.hash]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTickets = async () => {
|
||||
const { tickets } = await getOverviewTicketsAction(name);
|
||||
setTickets(tickets);
|
||||
};
|
||||
|
||||
fetchTickets();
|
||||
|
||||
const interval = setInterval(fetchTickets, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [name]);
|
||||
|
||||
return <TicketList title={name} tickets={tickets} />;
|
||||
};
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { DisplayError } from "app/_components/DisplayError";
|
||||
|
||||
type PageProps = {
|
||||
error: Error;
|
||||
};
|
||||
|
||||
export default function Page({ error }: PageProps) {
|
||||
return <DisplayError error={error} />;
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
import { Metadata } from "next";
|
||||
import { ZammadOverview } from "./_components/ZammadOverview";
|
||||
|
||||
const getSection = (overview: string) => {
|
||||
return overview.charAt(0).toUpperCase() + overview.slice(1);
|
||||
};
|
||||
|
||||
type MetadataProps = {
|
||||
params: Promise<{
|
||||
overview: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: MetadataProps): Promise<Metadata> {
|
||||
const { overview } = await params;
|
||||
const section = getSection(overview);
|
||||
|
||||
return {
|
||||
title: `CDR Link - ${section} Tickets`,
|
||||
};
|
||||
}
|
||||
|
||||
type PageProps = {
|
||||
params: Promise<{
|
||||
overview: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { overview } = await params;
|
||||
const section = getSection(overview);
|
||||
|
||||
return <ZammadOverview name={section} />;
|
||||
}
|
||||
|
|
@ -1,45 +1,10 @@
|
|||
import { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Home } from "./_components/Home";
|
||||
import { createLogger } from "@link-stack/logger";
|
||||
|
||||
const logger = createLogger('link-page');
|
||||
// import { getServerSession } from "app/_lib/authentication";
|
||||
// import { Home } from "@link-stack/leafcutter-ui";
|
||||
// import { getUserVisualizations } from "@link-stack/opensearch-common";
|
||||
// import { LeafcutterWrapper } from "@link-stack/leafcutter-ui";
|
||||
import { DefaultDashboard } from "./_components/DefaultDashboard";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "CDR Link - Home",
|
||||
};
|
||||
|
||||
export default async function Page() {
|
||||
const leafcutterEnabled = process.env.LEAFCUTTER_ENABLED === "true";
|
||||
const dashboardURL = process.env.LEAFCUTTER_DEFAULT_DASHBOARD_URL;
|
||||
|
||||
if (!leafcutterEnabled) {
|
||||
redirect("/overview/recent");
|
||||
}
|
||||
/*
|
||||
const session = await getServerSession();
|
||||
const {
|
||||
user: { email },
|
||||
}: any = session;
|
||||
*/
|
||||
let visualizations = [];
|
||||
/*
|
||||
try {
|
||||
visualizations = await getUserVisualizations(email ?? "none", 20);
|
||||
} catch (e) {
|
||||
logger.error({ meta: e.meta }, "Error metadata");
|
||||
}
|
||||
|
||||
return (
|
||||
<LeafcutterWrapper>
|
||||
<Home visualizations={visualizations} showWelcome={false} />
|
||||
</LeafcutterWrapper>
|
||||
);
|
||||
*/
|
||||
|
||||
return <Home url={dashboardURL} />;
|
||||
return <DefaultDashboard />;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
import { Metadata } from "next";
|
||||
import { ZammadWrapper } from "../../(main)/_components/ZammadWrapper";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Profile",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <ZammadWrapper path="/#profile" hideSidebar={false} />;
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { Metadata } from "next";
|
||||
import { ZammadWrapper } from "../../(main)/_components/ZammadWrapper";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Reporting",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return <ZammadWrapper path="#report" />;
|
||||
}
|
||||
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
"use server";
|
||||
|
||||
import { executeGraphQL, executeREST } from "app/_lib/zammad";
|
||||
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
|
||||
import { getTicketsByOverviewQuery } from "app/_graphql/getTicketsByOverviewQuery";
|
||||
import { createLogger } from "@link-stack/logger";
|
||||
|
||||
const logger = createLogger('link-overviews');
|
||||
|
||||
const overviewLookup = {
|
||||
Assigned: "My Assigned Tickets",
|
||||
Open: "Open Tickets",
|
||||
Urgent: "Escalated Tickets",
|
||||
Unassigned: "Unassigned & Open Tickets",
|
||||
Recent: "Recent Tickets",
|
||||
Pending: "Pending Reached Tickets",
|
||||
MyPending: "My Pending Reached Tickets",
|
||||
MySubscribed: "My Subscribed Tickets",
|
||||
};
|
||||
|
||||
export const getOverviewTicketCountsAction = async () => {
|
||||
try {
|
||||
const recent = await executeREST({ path: "/api/v1/recent_view" });
|
||||
const countResult = await executeGraphQL({
|
||||
query: getTicketOverviewCountsQuery,
|
||||
});
|
||||
const overviews = countResult?.ticketOverviews?.edges ?? [];
|
||||
const counts = overviews.reduce((acc: any, overview: any) => {
|
||||
const name = overview.node.name;
|
||||
const key = Object.keys(overviewLookup)
|
||||
.find((k) => overviewLookup[k] === name)
|
||||
?.toLowerCase();
|
||||
if (key) {
|
||||
acc[key] = overview.node.ticketCount ?? 0;
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
counts.recent = recent.length;
|
||||
|
||||
return counts;
|
||||
} catch (e) {
|
||||
logger.error({ error: e }, "Error occurred");
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const getOverviewTicketsAction = async (name: string) => {
|
||||
let tickets = [];
|
||||
|
||||
try {
|
||||
if (name === "Recent") {
|
||||
const recent = await executeREST({ path: "/api/v1/recent_view" });
|
||||
const uniqueIDs = new Set(recent.map((rec: any) => rec.o_id));
|
||||
for (const id of uniqueIDs) {
|
||||
const tkt = await executeREST({
|
||||
path: `/api/v1/tickets/${id}`,
|
||||
});
|
||||
tickets.push({
|
||||
...tkt,
|
||||
internalId: tkt.id,
|
||||
createdAt: tkt.created_at,
|
||||
updatedAt: tkt.updated_at,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const fullName = overviewLookup[name];
|
||||
const countResult = await executeGraphQL({
|
||||
query: getTicketOverviewCountsQuery,
|
||||
});
|
||||
const overviewID = countResult?.ticketOverviews?.edges?.find(
|
||||
(overview: any) => overview.node.name === fullName,
|
||||
)?.node?.id;
|
||||
|
||||
const ticketsResult = await executeGraphQL({
|
||||
query: getTicketsByOverviewQuery,
|
||||
variables: { overviewId: overviewID, pageSize: 250 },
|
||||
});
|
||||
|
||||
const edges = ticketsResult?.ticketsByOverview?.edges;
|
||||
if (edges) {
|
||||
tickets = edges.map((edge: any) => edge.node);
|
||||
}
|
||||
}
|
||||
|
||||
const sortedTickets = tickets.sort((a: any, b: any) => {
|
||||
if (a.internalId < b.internalId) {
|
||||
return 1;
|
||||
}
|
||||
if (a.internalId > b.internalId) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return { tickets: sortedTickets };
|
||||
} catch (e) {
|
||||
logger.error({ error: e }, "Error occurred");
|
||||
return { tickets, message: e.message ?? "" };
|
||||
}
|
||||
};
|
||||
|
|
@ -4,11 +4,9 @@ import { FC, PropsWithChildren } from "react";
|
|||
import { CssBaseline } from "@mui/material";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { I18n } from "react-polyglot";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFnsV3";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
||||
import { LicenseInfo } from "@mui/x-license";
|
||||
import { locales, LeafcutterProvider } from "@link-stack/leafcutter-ui";
|
||||
import { ZammadLoginProvider } from "./ZammadLoginProvider";
|
||||
|
||||
LicenseInfo.setLicenseKey(
|
||||
|
|
@ -16,18 +14,13 @@ LicenseInfo.setLicenseKey(
|
|||
);
|
||||
|
||||
export const MultiProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||
const messages: any = { en: locales.en, fr: locales.fr };
|
||||
const locale = "en";
|
||||
|
||||
return (
|
||||
<SessionProvider basePath="/link/api/auth">
|
||||
<CssBaseline />
|
||||
<ZammadLoginProvider>
|
||||
<CookiesProvider>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<I18n locale={locale} messages={messages[locale]}>
|
||||
<LeafcutterProvider>{children}</LeafcutterProvider>
|
||||
</I18n>
|
||||
{children}
|
||||
</LocalizationProvider>
|
||||
</CookiesProvider>
|
||||
</ZammadLoginProvider>
|
||||
|
|
|
|||
44
apps/link/app/_components/OpenSearchWrapper.tsx
Normal file
44
apps/link/app/_components/OpenSearchWrapper.tsx
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Iframe from "react-iframe";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface OpenSearchWrapperProps {
|
||||
url: string;
|
||||
margin?: number;
|
||||
}
|
||||
|
||||
export const OpenSearchWrapper: FC<OpenSearchWrapperProps> = ({
|
||||
url,
|
||||
margin = 50,
|
||||
}) => (
|
||||
<Box sx={{ position: "relative", marginTop: "-100px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100px",
|
||||
marginTop: "-20px",
|
||||
backgroundColor: "white",
|
||||
zIndex: 100,
|
||||
position: "relative",
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: `-${margin}px`,
|
||||
zIndex: 1,
|
||||
position: "relative",
|
||||
height: `calc(100vh + ${margin}px)`,
|
||||
}}
|
||||
>
|
||||
<Iframe
|
||||
id="opensearch"
|
||||
url={`/link/dashboards/${url}`}
|
||||
width="100%"
|
||||
height="100%"
|
||||
frameBorder={0}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
import { gql } from 'graphql-request';
|
||||
|
||||
export const getTicketsByOverviewQuery = gql`
|
||||
query ticketsByOverview($overviewId: ID!, $orderBy: String, $orderDirection: EnumOrderDirection, $cursor: String, $showPriority: Boolean = false, $showUpdatedBy: Boolean = false, $pageSize: Int = 10) {
|
||||
ticketsByOverview(
|
||||
overviewId: $overviewId
|
||||
orderBy: $orderBy
|
||||
orderDirection: $orderDirection
|
||||
after: $cursor
|
||||
first: $pageSize
|
||||
) {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
internalId
|
||||
number
|
||||
title
|
||||
createdAt
|
||||
updatedAt
|
||||
updatedBy @include(if: $showUpdatedBy) {
|
||||
id
|
||||
fullname
|
||||
}
|
||||
customer {
|
||||
id
|
||||
firstname
|
||||
lastname
|
||||
fullname
|
||||
}
|
||||
organization {
|
||||
id
|
||||
name
|
||||
}
|
||||
state {
|
||||
id
|
||||
name
|
||||
stateType {
|
||||
name
|
||||
}
|
||||
}
|
||||
group {
|
||||
id
|
||||
name
|
||||
}
|
||||
priority @include(if: $showPriority) {
|
||||
id
|
||||
name
|
||||
uiColor
|
||||
defaultCreate
|
||||
}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
endCursor
|
||||
hasNextPage
|
||||
}
|
||||
}
|
||||
}`;
|
||||
Loading…
Add table
Add a link
Reference in a new issue