Use server actions instead of client-side API calls
This commit is contained in:
parent
5a3127dcb0
commit
aa453954ed
30 changed files with 703 additions and 462 deletions
|
|
@ -4,6 +4,8 @@ import { FC } from "react";
|
|||
import { OpenSearchWrapper } from "@link-stack/leafcutter-ui";
|
||||
|
||||
export const Home: FC = () => (
|
||||
<OpenSearchWrapper url="/app/visualize#/edit/237b8f00-e6a0-11ee-94b3-d7b7409294e7?embed=true" marginTop="0"
|
||||
<OpenSearchWrapper
|
||||
url="/app/visualize#/edit/237b8f00-e6a0-11ee-94b3-d7b7409294e7?embed=true"
|
||||
marginTop="0"
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,21 +1,32 @@
|
|||
"use client";
|
||||
|
||||
import { FC, PropsWithChildren, useState } from "react";
|
||||
import { Grid } from "@mui/material";
|
||||
import { Grid, Box } from "@mui/material";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { SetupModeWarning } from "./SetupModeWarning";
|
||||
|
||||
export const InternalLayout: FC<PropsWithChildren> = ({ children }) => {
|
||||
interface InternalLayoutProps extends PropsWithChildren {
|
||||
setupModeActive: boolean;
|
||||
}
|
||||
|
||||
export const InternalLayout: FC<InternalLayoutProps> = ({
|
||||
children,
|
||||
setupModeActive,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
return (
|
||||
<Grid container direction="row">
|
||||
<Sidebar open={open} setOpen={setOpen} />
|
||||
<Grid
|
||||
item
|
||||
sx={{ ml: open ? "270px" : "70px", width: "100%", height: "100vh" }}
|
||||
>
|
||||
{children as any}
|
||||
<Box sx={{ position: "relative" }}>
|
||||
<SetupModeWarning setupModeActive={setupModeActive} />
|
||||
<Grid container direction="row">
|
||||
<Sidebar open={open} setOpen={setOpen} />
|
||||
<Grid
|
||||
item
|
||||
sx={{ ml: open ? "270px" : "70px", width: "100%", height: "100vh" }}
|
||||
>
|
||||
{children as any}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
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 { searchAllAction } from "@/app/_actions/search";
|
||||
import { colors } from "@link-stack/ui";
|
||||
|
||||
type SearchResultProps = {
|
||||
|
|
@ -42,8 +41,6 @@ const SearchInput = (params: any) => (
|
|||
);
|
||||
|
||||
const SearchResult: FC<SearchResultProps> = ({ props, option }) => {
|
||||
console.log({ option });
|
||||
|
||||
const { lightGrey, mediumGray, black, white } = colors;
|
||||
|
||||
return (
|
||||
|
|
@ -95,22 +92,20 @@ const SearchResult: FC<SearchResultProps> = ({ props, option }) => {
|
|||
|
||||
export const SearchBox: FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
const [selectedValue, setSelectedValue] = useState(null);
|
||||
const [searchTerms, setSearchTerms] = useState("");
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { data, error }: any = useSWR({
|
||||
document: searchQuery,
|
||||
variables: {
|
||||
search: searchTerms ?? "",
|
||||
limit: 50,
|
||||
},
|
||||
refreshInterval: 10000,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(false);
|
||||
}, [pathname]);
|
||||
const fetchSearchResults = async () => {
|
||||
const results = await searchAllAction(searchTerms ?? "", 50);
|
||||
setSearchResults(results);
|
||||
};
|
||||
|
||||
fetchSearchResults();
|
||||
}, [searchTerms]);
|
||||
|
||||
return (
|
||||
<Autocomplete
|
||||
|
|
@ -132,7 +127,7 @@ export const SearchBox: FC = () => {
|
|||
open={open}
|
||||
onOpen={() => setOpen(true)}
|
||||
noOptionsText="No results"
|
||||
options={data?.search ?? []}
|
||||
options={searchResults ?? []}
|
||||
getOptionLabel={(option: any) => {
|
||||
if (option) {
|
||||
return option.title;
|
||||
|
|
|
|||
29
apps/link/app/(main)/_components/SetupModeWarning.tsx
Normal file
29
apps/link/app/(main)/_components/SetupModeWarning.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
interface SetupModeWarningProps {
|
||||
setupModeActive: boolean;
|
||||
}
|
||||
|
||||
export const SetupModeWarning: FC<SetupModeWarningProps> = ({
|
||||
setupModeActive,
|
||||
}) =>
|
||||
setupModeActive ? (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "red",
|
||||
textAlign: "center",
|
||||
zIndex: 10000,
|
||||
position: "absolute",
|
||||
width: "100vw",
|
||||
top: 0,
|
||||
left: 0,
|
||||
}}
|
||||
>
|
||||
<Box component="h2" sx={{ color: "white", m: 0, p: 0 }}>
|
||||
Setup Mode Active
|
||||
</Box>
|
||||
</Box>
|
||||
) : null;
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import {
|
||||
Box,
|
||||
Grid,
|
||||
|
|
@ -33,7 +32,7 @@ import Link from "next/link";
|
|||
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 { getOverviewTicketCountsAction } from "app/_actions/overviews";
|
||||
import { SearchBox } from "./SearchBox";
|
||||
import { fonts } from "@link-stack/ui";
|
||||
|
||||
|
|
@ -185,33 +184,32 @@ interface SidebarProps {
|
|||
export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
const [overviewCounts, setOverviewCounts] = useState<any>(null);
|
||||
const { poppins } = fonts;
|
||||
const username = session?.user?.name || "User";
|
||||
// @ts-ignore
|
||||
const roles = session?.user?.roles || [];
|
||||
const { data: overviewData, error: overviewError }: any = useSWR(
|
||||
{
|
||||
document: getTicketOverviewCountsQuery,
|
||||
},
|
||||
{ refreshInterval: 10000 },
|
||||
);
|
||||
const findOverviewByName = (name: string) =>
|
||||
overviewData?.ticketOverviews?.edges?.find(
|
||||
(overview: any) => overview.node.name === name,
|
||||
)?.node?.id;
|
||||
const findOverviewCountByID = (id: string) =>
|
||||
overviewData?.ticketOverviews?.edges?.find(
|
||||
(overview: any) => overview.node.id === id,
|
||||
)?.node?.ticketCount ?? 0;
|
||||
const leafcutterEnabled = false;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCounts = async () => {
|
||||
const counts = await getOverviewTicketCountsAction();
|
||||
console.log({ counts });
|
||||
setOverviewCounts(counts);
|
||||
};
|
||||
|
||||
fetchCounts();
|
||||
|
||||
const interval = setInterval(fetchCounts, 10000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
const recentCount = 0;
|
||||
const assignedID = findOverviewByName("My Assigned Tickets");
|
||||
const assignedCount = findOverviewCountByID(assignedID);
|
||||
const openID = findOverviewByName("Open Tickets");
|
||||
const openCount = findOverviewCountByID(openID);
|
||||
const urgentID = findOverviewByName("Escalated Tickets");
|
||||
const urgentCount = findOverviewCountByID(urgentID);
|
||||
const unassignedID = findOverviewByName("Unassigned & Open Tickets");
|
||||
const unassignedCount = findOverviewCountByID(unassignedID);
|
||||
const assignedCount = overviewCounts?.["My Assigned Tickets"] ?? 0;
|
||||
const openCount = overviewCounts?.["Open Tickets"] ?? 0;
|
||||
const urgentCount = overviewCounts?.["Escalated Tickets"] ?? 0;
|
||||
const unassignedCount = overviewCounts?.["Unassigned & Open Tickets"] ?? 0;
|
||||
|
||||
const logout = () => {
|
||||
signOut({ callbackUrl: "/login" });
|
||||
|
|
@ -404,14 +402,16 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
|||
},
|
||||
}}
|
||||
>
|
||||
<MenuItem
|
||||
name="Home"
|
||||
href="/"
|
||||
Icon={CottageIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/")}
|
||||
open={open}
|
||||
/>
|
||||
{leafcutterEnabled && (
|
||||
<MenuItem
|
||||
name="Home"
|
||||
href="/"
|
||||
Icon={CottageIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/")}
|
||||
open={open}
|
||||
/>
|
||||
)}
|
||||
<MenuItem
|
||||
name="Tickets"
|
||||
href="/overview/recent"
|
||||
|
|
@ -504,14 +504,16 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
|||
selected={pathname.endsWith("/reporting")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Leafcutter"
|
||||
href="/leafcutter"
|
||||
Icon={InsightsIcon}
|
||||
iconSize={20}
|
||||
selected={false}
|
||||
open={open}
|
||||
/>
|
||||
{leafcutterEnabled && (
|
||||
<MenuItem
|
||||
name="Leafcutter"
|
||||
href="/leafcutter"
|
||||
Icon={InsightsIcon}
|
||||
iconSize={20}
|
||||
selected={false}
|
||||
open={open}
|
||||
/>
|
||||
)}
|
||||
<Collapse
|
||||
in={pathname.startsWith("/leafcutter")}
|
||||
timeout="auto"
|
||||
|
|
|
|||
|
|
@ -11,5 +11,11 @@ type LayoutProps = {
|
|||
};
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
return <InternalLayout>{children}</InternalLayout>;
|
||||
const setupModeActive = process.env.SETUP_MODE === "true";
|
||||
|
||||
return (
|
||||
<InternalLayout setupModeActive={setupModeActive}>
|
||||
{children}
|
||||
</InternalLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,19 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { useFormState } from "react-dom";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Grid } from "@mui/material";
|
||||
import { Dialog, Button, TextField, Autocomplete } from "@link-stack/ui";
|
||||
import {
|
||||
Dialog,
|
||||
Button,
|
||||
TextField,
|
||||
Autocomplete,
|
||||
Select,
|
||||
} from "@link-stack/ui";
|
||||
import { createTicketAction } from "app/_actions/tickets";
|
||||
import useSWR from "swr";
|
||||
import { getCustomersAction } from "app/_actions/users";
|
||||
import { getGroupsAction } from "app/_actions/groups";
|
||||
|
||||
interface TicketCreateDialogProps {
|
||||
open: boolean;
|
||||
|
|
@ -16,6 +24,8 @@ export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
|
|||
open,
|
||||
closeDialog,
|
||||
}) => {
|
||||
const [customers, setCustomers] = useState([]);
|
||||
const [groups, setGroups] = useState([]);
|
||||
const initialState = {
|
||||
messages: [],
|
||||
errors: [],
|
||||
|
|
@ -38,16 +48,41 @@ export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
|
|||
createTicketAction,
|
||||
initialState,
|
||||
);
|
||||
const { data: users, error: usersError }: any = useSWR({
|
||||
url: "/api/v1/users",
|
||||
method: "GET",
|
||||
});
|
||||
const customers =
|
||||
users?.filter((user: any) => user.role_ids.includes(3)) ?? [];
|
||||
const formattedCustomers = customers.map((customer: any) => ({
|
||||
label: customer.login,
|
||||
id: `${customer.id}`,
|
||||
}));
|
||||
const [liveFormState, setLiveFormState] = useState(formState);
|
||||
const updateFormState = (field: string, value: any) => {
|
||||
console.log({ value });
|
||||
const newState = { ...liveFormState };
|
||||
newState.values[field] = value;
|
||||
setLiveFormState(newState);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUsers = async () => {
|
||||
const result = await getCustomersAction();
|
||||
console.log({ result });
|
||||
setCustomers(result);
|
||||
};
|
||||
|
||||
fetchUsers();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchGroups = async () => {
|
||||
const result = await getGroupsAction();
|
||||
console.log({ result });
|
||||
setGroups(result);
|
||||
};
|
||||
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (formState.success) {
|
||||
closeDialog();
|
||||
}
|
||||
}, [formState.success, router]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
|
|
@ -74,11 +109,21 @@ export const TicketCreateDialog: FC<TicketCreateDialogProps> = ({
|
|||
>
|
||||
<Grid container direction="column" spacing={3}>
|
||||
<Grid item>
|
||||
<Autocomplete
|
||||
<Select
|
||||
name="groupId"
|
||||
label="Group"
|
||||
getOptions={() => groups as any}
|
||||
formState={liveFormState}
|
||||
updateFormState={updateFormState}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Select
|
||||
name="customerId"
|
||||
label="Customer"
|
||||
options={formattedCustomers}
|
||||
formState={formState}
|
||||
getOptions={() => customers as any}
|
||||
formState={liveFormState}
|
||||
updateFormState={updateFormState}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
|
|
|
|||
|
|
@ -1,113 +1,29 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { getOverviewTicketsAction } from "app/_actions/overviews";
|
||||
|
||||
import { TicketList } from "./TicketList";
|
||||
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
|
||||
import { getTicketsByOverviewQuery } from "app/_graphql/getTicketsByOverviewQuery";
|
||||
|
||||
type ZammadOverviewProps = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export const ZammadOverview: FC<ZammadOverviewProps> = ({ name }) => {
|
||||
const [overviewID, setOverviewID] = useState(null);
|
||||
const [tickets, setTickets] = useState([]);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
const { data: overviewData, error: overviewError }: any = useSWR(
|
||||
{
|
||||
document: getTicketOverviewCountsQuery,
|
||||
},
|
||||
{ refreshInterval: 10000 },
|
||||
);
|
||||
const { data: ticketData, error: ticketError }: any = useSWR(
|
||||
{
|
||||
document: getTicketsByOverviewQuery,
|
||||
variables: { overviewId: overviewID, pageSize: 250 },
|
||||
},
|
||||
{ refreshInterval: 10000 },
|
||||
);
|
||||
const overviewLookup = {
|
||||
Assigned: "My Assigned Tickets",
|
||||
Open: "Open Tickets",
|
||||
Urgent: "Escalated Tickets",
|
||||
Unassigned: "Unassigned & Open Tickets",
|
||||
};
|
||||
|
||||
const findOverviewByName = (name: string) => {
|
||||
const fullName = overviewLookup[name];
|
||||
return overviewData?.ticketOverviews?.edges?.find(
|
||||
(overview: any) => overview.node.name === fullName,
|
||||
)?.node?.id;
|
||||
};
|
||||
useEffect(() => {
|
||||
if (overviewData) {
|
||||
setOverviewID(findOverviewByName(name));
|
||||
}
|
||||
}, [overviewData, name]);
|
||||
console.log({
|
||||
name,
|
||||
overviewID,
|
||||
overviewData,
|
||||
overviewError,
|
||||
ticketData,
|
||||
ticketError,
|
||||
});
|
||||
|
||||
const restFetcher = (url: string) => fetch(url).then((r) => r.json());
|
||||
const { data: recent } = useSWR("/api/v1/recent_view", restFetcher);
|
||||
|
||||
const sortTickets = (tickets: any) => {
|
||||
return tickets.sort((a: any, b: any) => {
|
||||
if (a.internalId < b.internalId) {
|
||||
return 1;
|
||||
}
|
||||
if (a.internalId > b.internalId) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (name != "Recent") {
|
||||
const edges = ticketData?.ticketsByOverview?.edges;
|
||||
if (edges) {
|
||||
const nodes = edges.map((edge: any) => edge.node);
|
||||
console.log({ nodes });
|
||||
setError(null);
|
||||
setTickets(sortTickets(nodes));
|
||||
}
|
||||
|
||||
if (ticketError) {
|
||||
setError(ticketError);
|
||||
}
|
||||
}
|
||||
}, [ticketData, ticketError]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRecentTickets = async () => {
|
||||
if (name === "Recent" && recent) {
|
||||
let allTickets = [];
|
||||
for (const rec of recent) {
|
||||
const res = await fetch(`/api/v1/tickets/${rec.o_id}`);
|
||||
const tkt = await res.json();
|
||||
allTickets.push({
|
||||
...tkt,
|
||||
internalId: tkt.id,
|
||||
createdAt: tkt.created_at,
|
||||
updatedAt: tkt.updated_at,
|
||||
});
|
||||
}
|
||||
setTickets(sortTickets(allTickets));
|
||||
console.log({ allTickets });
|
||||
}
|
||||
const fetchTickets = async () => {
|
||||
const { tickets } = await getOverviewTicketsAction(name);
|
||||
setTickets(tickets);
|
||||
};
|
||||
fetchRecentTickets();
|
||||
|
||||
fetchTickets();
|
||||
|
||||
const interval = setInterval(fetchTickets, 20000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [name]);
|
||||
|
||||
const shouldRender = tickets && !error;
|
||||
|
||||
return shouldRender && <TicketList title={name} tickets={tickets} />;
|
||||
return <TicketList title={name} tickets={tickets} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export async function generateMetadata({
|
|||
const section = getSection(overview);
|
||||
|
||||
return {
|
||||
title: `Link - ${section} Tickets`,
|
||||
title: `CDR Link - ${section} Tickets`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Metadata } from "next";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getServerSession } from "app/_lib/authentication";
|
||||
import { Home } from "@link-stack/leafcutter-ui";
|
||||
import { getUserVisualizations } from "@link-stack/opensearch-common";
|
||||
|
|
@ -13,11 +14,14 @@ export default async function Page() {
|
|||
const {
|
||||
user: { email },
|
||||
}: any = session;
|
||||
const visualizations = await getUserVisualizations(email ?? "none", 20);
|
||||
// const visualizations = await getUserVisualizations(email ?? "none", 20);
|
||||
|
||||
redirect("/overview/recent");
|
||||
/*
|
||||
return (
|
||||
<LeafcutterWrapper>
|
||||
<Home visualizations={visualizations} showWelcome={false} />
|
||||
</LeafcutterWrapper>
|
||||
);
|
||||
*/
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import {
|
|||
DialogContent,
|
||||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useSWRConfig } from "swr";
|
||||
import { updateTicketMutation } from "app/_graphql/updateTicketMutation";
|
||||
import { createTicketArticleAction } from "app/_actions/tickets";
|
||||
|
||||
interface ArticleCreateDialogProps {
|
||||
ticketID: string;
|
||||
|
|
@ -30,7 +29,6 @@ export const ArticleCreateDialog: FC<ArticleCreateDialogProps> = ({
|
|||
const [body, setBody] = useState("");
|
||||
const backgroundColor = kind === "note" ? "#FFB620" : "#1982FC";
|
||||
const color = kind === "note" ? "black" : "white";
|
||||
const { fetcher } = useSWRConfig();
|
||||
const article = {
|
||||
body,
|
||||
type: kind,
|
||||
|
|
@ -42,15 +40,7 @@ export const ArticleCreateDialog: FC<ArticleCreateDialogProps> = ({
|
|||
}
|
||||
|
||||
const createArticle = async () => {
|
||||
await fetcher({
|
||||
document: updateTicketMutation,
|
||||
variables: {
|
||||
ticketId: `gid://zammad/Ticket/${ticketID}`,
|
||||
input: {
|
||||
article,
|
||||
},
|
||||
},
|
||||
});
|
||||
await createTicketArticleAction(ticketID, article);
|
||||
closeDialog();
|
||||
setBody("");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
||||
import { getTicketArticlesQuery } from "app/_graphql/getTicketArticlesQuery";
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { getTicketAction, getTicketArticlesAction } from "app/_actions/tickets";
|
||||
import { Grid, Box, Typography } from "@mui/material";
|
||||
import { Button, fonts, colors } from "@link-stack/ui";
|
||||
|
||||
import "@chatscope/chat-ui-kit-styles/dist/default/styles.min.css";
|
||||
import {
|
||||
MainContainer,
|
||||
|
|
@ -22,41 +19,46 @@ interface TicketDetailProps {
|
|||
}
|
||||
|
||||
export const TicketDetail: FC<TicketDetailProps> = ({ id }) => {
|
||||
const [ticket, setTicket] = useState<any>(null);
|
||||
const [ticketArticles, setTicketArticles] = useState<any>(null);
|
||||
const { poppins, roboto } = fonts;
|
||||
const { veryLightGray, lightGray } = colors;
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [articleKind, setArticleKind] = useState("note");
|
||||
const { data: ticketData, error: ticketError }: any = useSWR(
|
||||
{
|
||||
document: getTicketQuery,
|
||||
variables: { ticketId: `gid://zammad/Ticket/${id}` },
|
||||
},
|
||||
{ refreshInterval: 10000 },
|
||||
);
|
||||
const { data: ticketArticlesData, error: ticketArticlesError }: any = useSWR(
|
||||
{
|
||||
document: getTicketArticlesQuery,
|
||||
variables: { ticketId: `gid://zammad/Ticket/${id}` },
|
||||
},
|
||||
{ refreshInterval: 2000 },
|
||||
);
|
||||
|
||||
const { data: recentViewData, error: recentViewError }: any = useSWR({
|
||||
url: "/api/v1/recent_view",
|
||||
method: "POST",
|
||||
body: JSON.stringify({ object: "Ticket", o_id: id }),
|
||||
});
|
||||
useEffect(() => {
|
||||
const fetchTicket = async () => {
|
||||
const result = await getTicketAction(id);
|
||||
setTicket(result);
|
||||
};
|
||||
|
||||
fetchTicket();
|
||||
|
||||
const interval = setInterval(fetchTicket, 20000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTicketArticles = async () => {
|
||||
const result = await getTicketArticlesAction(id);
|
||||
setTicketArticles(result);
|
||||
};
|
||||
|
||||
fetchTicketArticles();
|
||||
|
||||
const interval = setInterval(fetchTicketArticles, 2000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [id]);
|
||||
|
||||
const closeDialog = () => setDialogOpen(false);
|
||||
|
||||
const ticket = ticketData?.ticket;
|
||||
const ticketArticles = ticketArticlesData?.ticketArticles;
|
||||
const firstArticle = ticketArticles?.edges[0]?.node;
|
||||
const firstArticleKind = firstArticle?.type?.name ?? "phone";
|
||||
const firstEmailSender = firstArticle?.from?.parsed?.[0]?.emailAddress ?? "";
|
||||
const recipient = firstEmailSender;
|
||||
const shouldRender =
|
||||
ticketData && !ticketError && ticketArticlesData && !ticketArticlesError;
|
||||
const shouldRender = !!ticket && !!ticketArticles;
|
||||
|
||||
return (
|
||||
shouldRender && (
|
||||
|
|
|
|||
|
|
@ -5,57 +5,87 @@ import { Grid, Box, MenuItem } from "@mui/material";
|
|||
import { useFormState } from "react-dom";
|
||||
import { Select, Button } from "@link-stack/ui";
|
||||
import { MuiChipsInput } from "mui-chips-input";
|
||||
import useSWR, { useSWRConfig } from "swr";
|
||||
import { getTicketQuery } from "app/_graphql/getTicketQuery";
|
||||
import { DatePicker } from "@mui/x-date-pickers/DatePicker";
|
||||
import { updateTicketAction } from "app/_actions/tickets";
|
||||
import {
|
||||
updateTicketAction,
|
||||
getTicketAction,
|
||||
getTicketStatesAction,
|
||||
getTicketPrioritiesAction,
|
||||
getTicketTagsAction,
|
||||
} from "app/_actions/tickets";
|
||||
import { getAgentsAction } from "app/_actions/users";
|
||||
import { getGroupsAction } from "app/_actions/groups";
|
||||
|
||||
interface TicketEditProps {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
||||
const [ticket, setTicket] = useState<any>();
|
||||
const [ticketStates, setTicketStates] = useState<any>();
|
||||
const [ticketPriorities, setTicketPriorities] = useState<any>();
|
||||
const [groups, setGroups] = useState<any>();
|
||||
const [tags, setTags] = useState<any>();
|
||||
const [agents, setAgents] = useState<any>();
|
||||
const selectedTags = [];
|
||||
const pendingVisible = false;
|
||||
const pendingDate = new Date();
|
||||
const handleDelete = () => {
|
||||
console.info("You clicked the delete icon.");
|
||||
};
|
||||
const restFetcher = (url: string) => fetch(url).then((r) => r.json());
|
||||
|
||||
const { data: groups } = useSWR("/api/v1/groups", restFetcher);
|
||||
const { data: users } = useSWR("/api/v1/users", restFetcher);
|
||||
const { data: states } = useSWR("/api/v1/ticket_states", restFetcher);
|
||||
const { data: priorities } = useSWR("/api/v1/ticket_priorities", restFetcher);
|
||||
const { data: recent } = useSWR("/api/v1/recent_view", restFetcher);
|
||||
|
||||
// const { data: tags } = useSWR("/api/v1/tags", restFetcher);
|
||||
const filteredStates = states?.filter(
|
||||
const filteredStates = ticketStates?.filter(
|
||||
(state: any) => !["new", "merged", "removed"].includes(state.name),
|
||||
);
|
||||
const agents = users?.filter((user: any) => user.role_ids.includes(2)) ?? [];
|
||||
const { fetcher } = useSWRConfig();
|
||||
const { data: ticketData, error: ticketError }: any = useSWR(
|
||||
{
|
||||
document: getTicketQuery,
|
||||
variables: { ticketId: `gid://zammad/Ticket/${id}` },
|
||||
},
|
||||
{ refreshInterval: 10000 },
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const ticket = ticketData?.ticket;
|
||||
if (ticket) {
|
||||
const groupID = ticket.group.id?.split("/").pop();
|
||||
// setSelectedGroup(groupID);
|
||||
const ownerID = ticket.owner.id?.split("/").pop();
|
||||
// setSelectedOwner(ownerID);
|
||||
const priorityID = ticket.priority.id?.split("/").pop();
|
||||
// setSelectedPriority(priorityID);
|
||||
const stateID = ticket.state.id?.split("/").pop();
|
||||
// setSelectedState(stateID);
|
||||
// setSelectedTags(ticket.tags);
|
||||
}
|
||||
}, [ticketData, ticketError]);
|
||||
const fetchAgents = async () => {
|
||||
const result = await getAgentsAction();
|
||||
console.log({ result });
|
||||
setAgents(result);
|
||||
};
|
||||
|
||||
const fetchGroups = async () => {
|
||||
const result = await getGroupsAction();
|
||||
console.log({ result });
|
||||
setGroups(result);
|
||||
};
|
||||
|
||||
const fetchTicketStates = async () => {
|
||||
const result = await getTicketStatesAction();
|
||||
console.log({ result });
|
||||
setTicketStates(result);
|
||||
};
|
||||
|
||||
const fetchTicketPriorities = async () => {
|
||||
const result = await getTicketPrioritiesAction();
|
||||
console.log({ result });
|
||||
setTicketPriorities(result);
|
||||
};
|
||||
|
||||
const fetchTicketTags = async () => {
|
||||
const result = await getTicketTagsAction();
|
||||
console.log({ result });
|
||||
setTags(result);
|
||||
};
|
||||
|
||||
fetchTicketStates();
|
||||
fetchTicketPriorities();
|
||||
fetchTicketTags();
|
||||
fetchAgents();
|
||||
fetchGroups();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTicket = async () => {
|
||||
const result = await getTicketAction(id);
|
||||
console.log({ result });
|
||||
setTicket(result);
|
||||
};
|
||||
|
||||
fetchTicket();
|
||||
}, []);
|
||||
|
||||
/*
|
||||
useEffect(() => {
|
||||
|
|
@ -97,10 +127,10 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
errors: [],
|
||||
values: {
|
||||
customer: "",
|
||||
group: "",
|
||||
owner: "",
|
||||
priority: "",
|
||||
state: "",
|
||||
group: ticket.group.id?.split("/").pop(),
|
||||
owner: ticket.owner.id?.split("/").pop(),
|
||||
priority: ticket.priority.id?.split("/").pop(),
|
||||
state: ticket.state.id?.split("/").pop(),
|
||||
tags: [],
|
||||
title: "",
|
||||
article: {
|
||||
|
|
@ -114,7 +144,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
updateTicketAction,
|
||||
initialState,
|
||||
);
|
||||
const shouldRender = ticketData && !ticketError;
|
||||
const shouldRender = !!ticket;
|
||||
|
||||
return (
|
||||
shouldRender && (
|
||||
|
|
@ -220,7 +250,7 @@ export const TicketEdit: FC<TicketEditProps> = ({ id }) => {
|
|||
label="Priority"
|
||||
formState={formState}
|
||||
getOptions={() =>
|
||||
priorities?.map((priority: any) => ({
|
||||
ticketPriorities?.map((priority: any) => ({
|
||||
value: priority.id,
|
||||
label: priority.name,
|
||||
})) ?? []
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
type PageProps = {
|
||||
params: {
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default function Page({ params: { id } }: PageProps) {
|
||||
return <div>Page</div>;
|
||||
}
|
||||
|
||||
/*
|
||||
import { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import { NextPage } from "next";
|
||||
import { Grid } from "@mui/material";
|
||||
import { TicketDetail } from "../_components/TicketDetail";
|
||||
import { TicketEdit } from "../_components/TicketEdit";
|
||||
import { getTicketQuery } from "../_graphql/getTicketQuery";
|
||||
|
||||
type TicketProps = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const Ticket: NextPage<TicketProps> = ({ id }) => {
|
||||
const { data: ticketData, error: ticketError }: any = useSWR(
|
||||
{
|
||||
document: getTicketQuery,
|
||||
variables: { ticketId: `gid://zammad/Ticket/${id}` },
|
||||
},
|
||||
{ refreshInterval: 100000 }
|
||||
);
|
||||
|
||||
const shouldRender = !ticketError && ticketData;
|
||||
|
||||
return (
|
||||
<>
|
||||
{shouldRender && (
|
||||
<Grid container spacing={0} sx={{ height: "100vh" }} direction="row">
|
||||
<Grid item sx={{ height: "100vh" }} xs={9}>
|
||||
<TicketDetail ticket={ticketData.ticket} />
|
||||
</Grid>
|
||||
<Grid item xs={3} sx={{ height: "100vh" }}>
|
||||
<TicketEdit ticket={ticketData.ticket} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{ticketError && <div>{ticketError.toString()}</div>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const { id } = context.query;
|
||||
return { props: { id } };
|
||||
};
|
||||
|
||||
|
||||
export default Ticket;
|
||||
*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue