Added modal for input link
This commit is contained in:
parent
f16e356555
commit
bf7f8ebbf7
|
|
@ -3,7 +3,7 @@ import React from 'react'
|
||||||
export default async function Dashboard() {
|
export default async function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Dashboard</h1>
|
<h1 className='font-bold bg-red-500'>Dashboard</h1>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
src/app/dashboard/reseller-office-365/list/page.tsx
Normal file
39
src/app/dashboard/reseller-office-365/list/page.tsx
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
import getUserRoles from "@/modules/auth/utils/getUserRoles";
|
||||||
|
import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
|
||||||
|
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
||||||
|
import getAllLinkRequests from "@/modules/resellerOffice365/actions/getAllLinkRequests";
|
||||||
|
import getLinkRequests from "@/modules/resellerOffice365/actions/getLinkRequests";
|
||||||
|
import ListOfRequestTable from "@/modules/resellerOffice365/tables/ListOfRequestTable/ListOfRequestTable";
|
||||||
|
import RequestTable from "@/modules/resellerOffice365/tables/RequestTable/RequestTable";
|
||||||
|
import { Card, Stack, Title } from "@mantine/core";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export default async function RequestLinkPage() {
|
||||||
|
const permissions = await checkMultiplePermissions({
|
||||||
|
create: "office-365-link.create",
|
||||||
|
readAll: "office-365-link.readAll",
|
||||||
|
read: "office-365-link.read",
|
||||||
|
update: "office-365-link.update",
|
||||||
|
delete: "office-365-link.delete",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permissions.readAll) notFound();
|
||||||
|
|
||||||
|
const data = await getAllLinkRequests();
|
||||||
|
if (!data.success) {
|
||||||
|
//todo: handle error
|
||||||
|
console.error(data.error);
|
||||||
|
throw new Error("Error while fetch data");
|
||||||
|
}
|
||||||
|
const tableData = data.data;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack>
|
||||||
|
<Title order={1}>List Link Office 365</Title>
|
||||||
|
<Card>
|
||||||
|
<ListOfRequestTable permissions={permissions} tableData={tableData} />
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -22,7 +22,7 @@ export default async function RequestLinkPage() {
|
||||||
if (!data.success){
|
if (!data.success){
|
||||||
//todo: handle error
|
//todo: handle error
|
||||||
console.error(data.error)
|
console.error(data.error)
|
||||||
throw new Error("Error while fetch permission")
|
throw new Error("Error while fetch data")
|
||||||
}
|
}
|
||||||
const tableData = data.data
|
const tableData = data.data
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ declare global {
|
||||||
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
|
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prisma = globalThis.prisma ?? prismaClientSingleton();
|
const db = globalThis.prisma ?? prismaClientSingleton();
|
||||||
|
|
||||||
export default prisma;
|
export default db;
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== "production") globalThis.prisma = prisma;
|
if (process.env.NODE_ENV !== "production") globalThis.prisma = db;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ declare global {
|
||||||
var prisma: undefined | ReturnType<typeof prismaClientSingleton>
|
var prisma: undefined | ReturnType<typeof prismaClientSingleton>
|
||||||
}
|
}
|
||||||
|
|
||||||
const prisma = globalThis.prisma ?? prismaClientSingleton()
|
const db = globalThis.prisma ?? prismaClientSingleton()
|
||||||
|
|
||||||
export default prisma
|
export default prisma
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ const sidebarMenus: SidebarMenu[] = [
|
||||||
allowedPermissions: ["*"],
|
allowedPermissions: ["*"],
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
label: "Request Link",
|
label: "My Request Links",
|
||||||
link: "/reseller-office-365/request",
|
link: "/reseller-office-365/request",
|
||||||
allowedRoles: ["*"]
|
allowedRoles: ["*"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Respond Request Link",
|
label: "Process Request Link",
|
||||||
link: "#",
|
link: "/reseller-office-365/list",
|
||||||
allowedRoles: ["*"]
|
allowedRoles: ["*"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import mapObjectToFirstValue from "@/utils/mapObjectToFirstValue";
|
||||||
import db from "@/core/db";
|
import db from "@/core/db";
|
||||||
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
|
import getCurrentUser from "@/modules/auth/utils/getCurrentUser";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import {server} from "../../../../server/socket";
|
|
||||||
|
|
||||||
export default async function createLinkRequest(
|
export default async function createLinkRequest(
|
||||||
formData: RequestLinkForm
|
formData: RequestLinkForm
|
||||||
|
|
@ -55,8 +54,6 @@ export default async function createLinkRequest(
|
||||||
|
|
||||||
revalidatePath(".")
|
revalidatePath(".")
|
||||||
|
|
||||||
server.publish(`mwrl-${currentUser.id}`, "update")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message:
|
message:
|
||||||
|
|
|
||||||
55
src/modules/resellerOffice365/actions/getAllLinkRequests.ts
Normal file
55
src/modules/resellerOffice365/actions/getAllLinkRequests.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
||||||
|
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
||||||
|
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
||||||
|
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
||||||
|
import db from "@/core/db";
|
||||||
|
import RequestLinkWithIssuerData from "../types/RequestLinkWithIssuerData";
|
||||||
|
|
||||||
|
export default async function getAllLinkRequests(): Promise<
|
||||||
|
ServerResponseAction<RequestLinkWithIssuerData[]>
|
||||||
|
> {
|
||||||
|
try {
|
||||||
|
//TODO: Fix permission check
|
||||||
|
if (!(await checkPermission("authenticated-only")))
|
||||||
|
return unauthorized();
|
||||||
|
|
||||||
|
const requestLinks = await db.office365LinkRequest.findMany({
|
||||||
|
orderBy: [
|
||||||
|
{
|
||||||
|
status: "desc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
requestedAt: "desc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
creator: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
photoProfile: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
status: true,
|
||||||
|
requestedAt: true,
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
links: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: requestLinks.map((item) => ({
|
||||||
|
...item,
|
||||||
|
userCount: item._count.links,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return handleCatch(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
"use server"
|
||||||
|
import checkPermission from "@/modules/dashboard/services/checkPermission";
|
||||||
|
import ServerResponseAction from "@/modules/dashboard/types/ServerResponseAction";
|
||||||
|
import handleCatch from "@/modules/dashboard/utils/handleCatch";
|
||||||
|
import unauthorized from "@/modules/dashboard/utils/unauthorized";
|
||||||
|
import RequestLinkWithIssuerData from "../types/RequestLinkWithIssuerData";
|
||||||
|
import db from "@/core/db";
|
||||||
|
import { string } from "zod";
|
||||||
|
import notFound from "@/modules/dashboard/utils/notFound";
|
||||||
|
|
||||||
|
async function getOffice365LinkRequestData(id: string) {
|
||||||
|
const data = await db.office365LinkRequest.findFirst({
|
||||||
|
where: { id },
|
||||||
|
select: {
|
||||||
|
acceptedAt: true,
|
||||||
|
cancelledAt: true,
|
||||||
|
creator: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
id: true,
|
||||||
|
links: {
|
||||||
|
select: {
|
||||||
|
activePeriod: true,
|
||||||
|
email: true,
|
||||||
|
id: true,
|
||||||
|
link: true,
|
||||||
|
numberOfUsers: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rejectedAt: true,
|
||||||
|
requestedAt: true,
|
||||||
|
status: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function getLinkRequestDataById(
|
||||||
|
id: string
|
||||||
|
): Promise<
|
||||||
|
ServerResponseAction<
|
||||||
|
NonNullable<Awaited<ReturnType<typeof getOffice365LinkRequestData>>>
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
try {
|
||||||
|
//TODO: Adjust permission
|
||||||
|
if (!(await checkPermission("authenticated-only")))
|
||||||
|
return unauthorized();
|
||||||
|
|
||||||
|
const data = await getOffice365LinkRequestData(id);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return notFound({
|
||||||
|
message: "The requested link request item is not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return handleCatch(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable react-hooks/exhaustive-deps */
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Button,
|
Button,
|
||||||
|
|
@ -13,9 +14,10 @@ import {
|
||||||
TextInput,
|
TextInput,
|
||||||
Loader,
|
Loader,
|
||||||
Text,
|
Text,
|
||||||
|
Skeleton,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useForm, zodResolver } from "@mantine/form";
|
import { useForm, zodResolver } from "@mantine/form";
|
||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
TbAt,
|
TbAt,
|
||||||
TbCalendarTime,
|
TbCalendarTime,
|
||||||
|
|
@ -30,27 +32,82 @@ import withServerAction from "@/modules/dashboard/utils/withServerAction";
|
||||||
import createLinkRequest from "../actions/createLinkRequest";
|
import createLinkRequest from "../actions/createLinkRequest";
|
||||||
import { notifications } from "@mantine/notifications";
|
import { notifications } from "@mantine/notifications";
|
||||||
import DashboardError from "@/modules/dashboard/errors/DashboardError";
|
import DashboardError from "@/modules/dashboard/errors/DashboardError";
|
||||||
|
import getLinkRequestDataById from "../actions/getLinkRequestDataById";
|
||||||
|
import { isPagesAPIRouteMatch } from "next/dist/server/future/route-matches/pages-api-route-match";
|
||||||
|
|
||||||
export interface ModalProps {
|
export interface ModalProps {
|
||||||
title: string;
|
title: string;
|
||||||
opened: boolean;
|
opened: boolean;
|
||||||
readonly: boolean;
|
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
type: "create" | "detail" | "waiting" | "input link";
|
||||||
|
detailId: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RequestModal(props: ModalProps) {
|
export default function RequestModal(props: ModalProps) {
|
||||||
const [formState, setFormState] = useState<
|
const [formState, setFormState] = useState<
|
||||||
"idle" | "submitting" | "waiting"
|
"idle" | "submitting" | "waiting" | "fetching" | "error"
|
||||||
>("idle");
|
>("idle");
|
||||||
|
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
|
if (formState === "submitting") return; //prevents closing
|
||||||
|
//reset state
|
||||||
|
setErrorMessage("");
|
||||||
|
setFormState("idle");
|
||||||
|
form.reset();
|
||||||
props.onClose ? props.onClose() : "";
|
props.onClose ? props.onClose() : "";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDataById = async (id: string) => {
|
||||||
|
const { data } = await withServerAction(getLinkRequestDataById, id);
|
||||||
|
if (!props.opened) return;
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (props.type) {
|
||||||
|
case "input link": {
|
||||||
|
if (!props.detailId || !props.opened) return;
|
||||||
|
setFormState("fetching");
|
||||||
|
fetchDataById(props.detailId)
|
||||||
|
.then((data) => {
|
||||||
|
if (!data) {
|
||||||
|
closeModal();
|
||||||
|
notifications.show({
|
||||||
|
message:
|
||||||
|
"The returned data from server is empty. Please try again",
|
||||||
|
color: "red",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
form.setValues({
|
||||||
|
numberOfLinks: data.links.length,
|
||||||
|
id: data.id,
|
||||||
|
details: data.links.map((item) => ({
|
||||||
|
activePeriod: item.activePeriod,
|
||||||
|
email: item.email,
|
||||||
|
endUserQty: item.numberOfUsers,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
setErrorMessage(e.message);
|
||||||
|
} else {
|
||||||
|
setErrorMessage("Unkown error occured");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setFormState("idle");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [props]);
|
||||||
|
|
||||||
const form = useForm<RequestLinkForm>({
|
const form = useForm<RequestLinkForm>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
id: undefined,
|
||||||
numberOfLinks: 1,
|
numberOfLinks: 1,
|
||||||
details: [
|
details: [
|
||||||
{
|
{
|
||||||
|
|
@ -90,51 +147,67 @@ export default function RequestModal(props: ModalProps) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const disableChange = formState !== "idle";
|
|
||||||
|
|
||||||
const handleSubmit = (values: RequestLinkForm) => {
|
const handleSubmit = (values: RequestLinkForm) => {
|
||||||
const submitableState = ["idle"];
|
const submitableState: (typeof formState)[] = ["idle"];
|
||||||
|
|
||||||
if (!submitableState.includes(formState)) return; //prevent submit
|
if (!submitableState.includes(formState)) return; //prevent submit when not in subitable state
|
||||||
|
|
||||||
setFormState("submitting");
|
setFormState("submitting");
|
||||||
|
|
||||||
withServerAction(createLinkRequest, values)
|
switch (props.type) {
|
||||||
.then((response) => {
|
case "create": {
|
||||||
notifications.show({
|
withServerAction(createLinkRequest, values)
|
||||||
message: response.message,
|
.then((response) => {
|
||||||
color: "green",
|
notifications.show({
|
||||||
});
|
message: response.message,
|
||||||
setFormState("waiting");
|
color: "green",
|
||||||
})
|
});
|
||||||
.catch((e) => {
|
setFormState("waiting");
|
||||||
if (e instanceof DashboardError) {
|
})
|
||||||
if (e.errorCode === "INVALID_FORM_DATA") {
|
.catch((e) => {
|
||||||
if (e.formErrors) {
|
if (e instanceof DashboardError) {
|
||||||
form.setErrors(e.formErrors);
|
if (e.errorCode === "INVALID_FORM_DATA") {
|
||||||
|
if (e.formErrors) {
|
||||||
|
form.setErrors(e.formErrors);
|
||||||
|
} else {
|
||||||
|
setErrorMessage(e.message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setErrorMessage(
|
||||||
|
`ERROR: ${e.message} (${e.errorCode})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (e instanceof Error) {
|
||||||
|
setErrorMessage(`ERROR: ${e.message}`);
|
||||||
} else {
|
} else {
|
||||||
setErrorMessage(e.message);
|
setErrorMessage(
|
||||||
|
`Unkown error is occured. Please contact administrator`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
setErrorMessage(`ERROR: ${e.message} (${e.errorCode})`);
|
|
||||||
}
|
|
||||||
} else if (e instanceof Error) {
|
|
||||||
setErrorMessage(`ERROR: ${e.message}`);
|
|
||||||
} else {
|
|
||||||
setErrorMessage(
|
|
||||||
`Unkown error is occured. Please contact administrator`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setFormState("idle");
|
setFormState("idle");
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "input link": {
|
||||||
|
//TODO: Handle add link
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disableChange = formState !== "idle";
|
||||||
|
const readonly = props.type === "input link";
|
||||||
|
const showSkeleton = formState === "fetching";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
size="sm"
|
size="sm"
|
||||||
opened={props.opened}
|
opened={props.opened}
|
||||||
title={formState === "waiting" ? "Link Request Detail" : "Create New Request"}
|
title={
|
||||||
|
formState === "waiting"
|
||||||
|
? "Link Request Detail"
|
||||||
|
: "Create New Request"
|
||||||
|
}
|
||||||
onClose={closeModal}
|
onClose={closeModal}
|
||||||
scrollAreaComponent={ScrollArea.Autosize}
|
scrollAreaComponent={ScrollArea.Autosize}
|
||||||
>
|
>
|
||||||
|
|
@ -146,16 +219,21 @@ export default function RequestModal(props: ModalProps) {
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<NumberInput
|
{errorMessage && <Alert color="red">{errorMessage}</Alert>}
|
||||||
label="Please input the number of links you request"
|
|
||||||
min={1}
|
<Skeleton visible={showSkeleton}>
|
||||||
max={3}
|
<NumberInput
|
||||||
allowDecimal={false}
|
label="Please input the number of links you request"
|
||||||
clampBehavior="strict"
|
min={1}
|
||||||
leftSection={<TbLink />}
|
max={3}
|
||||||
disabled={disableChange}
|
allowDecimal={false}
|
||||||
{...form.getInputProps("numberOfLinks")}
|
clampBehavior="strict"
|
||||||
/>
|
leftSection={<TbLink />}
|
||||||
|
disabled={disableChange}
|
||||||
|
readOnly={readonly}
|
||||||
|
{...form.getInputProps("numberOfLinks")}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
|
||||||
<Divider
|
<Divider
|
||||||
label="End User Information"
|
label="End User Information"
|
||||||
|
|
@ -165,39 +243,60 @@ export default function RequestModal(props: ModalProps) {
|
||||||
<Stack>
|
<Stack>
|
||||||
{form.values.details.map((item, i) => (
|
{form.values.details.map((item, i) => (
|
||||||
<Fieldset key={i} legend={`Information ${i + 1}`}>
|
<Fieldset key={i} legend={`Information ${i + 1}`}>
|
||||||
<TextInput
|
<Stack gap="xs">
|
||||||
leftSection={<TbAt />}
|
<Skeleton visible={showSkeleton}>
|
||||||
label="Email"
|
<TextInput
|
||||||
disabled={disableChange}
|
leftSection={<TbAt />}
|
||||||
{...form.getInputProps(
|
label="Email"
|
||||||
`details.${i}.email`
|
readOnly={readonly}
|
||||||
|
disabled={disableChange}
|
||||||
|
{...form.getInputProps(
|
||||||
|
`details.${i}.email`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
<Flex gap="md">
|
||||||
|
<Skeleton visible={showSkeleton}>
|
||||||
|
<Select
|
||||||
|
data={
|
||||||
|
resellerOffice365Config.activePeriods
|
||||||
|
}
|
||||||
|
label="Active Period"
|
||||||
|
disabled={disableChange}
|
||||||
|
readOnly={readonly}
|
||||||
|
leftSection={<TbCalendarTime />}
|
||||||
|
{...form.getInputProps(
|
||||||
|
`details.${i}.activePeriod`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
<Skeleton visible={showSkeleton}>
|
||||||
|
<NumberInput
|
||||||
|
label="End User Quantity"
|
||||||
|
leftSection={<TbUsers />}
|
||||||
|
min={1}
|
||||||
|
max={5}
|
||||||
|
disabled={disableChange}
|
||||||
|
allowDecimal={false}
|
||||||
|
readOnly={readonly}
|
||||||
|
clampBehavior="strict"
|
||||||
|
{...form.getInputProps(
|
||||||
|
`details.${i}.endUserQty`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
|
</Flex>
|
||||||
|
{["input link", "detail"].includes(
|
||||||
|
props.type
|
||||||
|
) && (
|
||||||
|
<Skeleton visible={showSkeleton}>
|
||||||
|
<TextInput
|
||||||
|
label="Activation Link"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Skeleton>
|
||||||
)}
|
)}
|
||||||
/>
|
</Stack>
|
||||||
<Flex gap="md">
|
|
||||||
<Select
|
|
||||||
data={
|
|
||||||
resellerOffice365Config.activePeriods
|
|
||||||
}
|
|
||||||
label="Active Period"
|
|
||||||
disabled={disableChange}
|
|
||||||
leftSection={<TbCalendarTime />}
|
|
||||||
{...form.getInputProps(
|
|
||||||
`details.${i}.activePeriod`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<NumberInput
|
|
||||||
label="End User Quantity"
|
|
||||||
leftSection={<TbUsers />}
|
|
||||||
min={1}
|
|
||||||
max={5}
|
|
||||||
disabled={disableChange}
|
|
||||||
allowDecimal={false}
|
|
||||||
clampBehavior="strict"
|
|
||||||
{...form.getInputProps(
|
|
||||||
`details.${i}.endUserQty`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Flex>
|
|
||||||
</Fieldset>
|
</Fieldset>
|
||||||
))}
|
))}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
@ -211,7 +310,7 @@ export default function RequestModal(props: ModalProps) {
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</Button>
|
</Button>
|
||||||
{(!props.readonly || formState === "waiting") && (
|
{formState === "waiting" && (
|
||||||
<Button
|
<Button
|
||||||
variant="filled"
|
variant="filled"
|
||||||
leftSection={<TbDeviceFloppy size={20} />}
|
leftSection={<TbDeviceFloppy size={20} />}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
"use client";
|
||||||
|
import DashboardTable from "@/modules/dashboard/components/DashboardTable";
|
||||||
|
import { Button, Flex, Text } from "@mantine/core";
|
||||||
|
import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||||
|
import React, { ReactNode, useState } from "react";
|
||||||
|
import { TbPlus } from "react-icons/tb";
|
||||||
|
import createColumns from "./columns";
|
||||||
|
import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
|
||||||
|
import RequestLink from "../../types/RequestLink";
|
||||||
|
import RequestModal, { ModalProps } from "../../modals/RequestModal";
|
||||||
|
import RequestLinkWithIssuerData from "../../types/RequestLinkWithIssuerData";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
permissions: Partial<CrudPermissions>;
|
||||||
|
tableData: RequestLinkWithIssuerData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultModalProps: ModalProps = {
|
||||||
|
opened: false,
|
||||||
|
title: "Create new Link",
|
||||||
|
type: "create",
|
||||||
|
detailId: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ListOfRequestTable(props: Props) {
|
||||||
|
const [modalProps, setModalProps] = useState<ModalProps>(defaultModalProps);
|
||||||
|
// const [openModal, setOpenModal] = useState(false);
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data: props.tableData,
|
||||||
|
columns: createColumns({
|
||||||
|
permissions: props.permissions,
|
||||||
|
actions: {
|
||||||
|
detail: (id) => {
|
||||||
|
openFormModal(id);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
defaultColumn: {
|
||||||
|
cell: (props) => <Text>{props.getValue() as ReactNode}</Text>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const openFormModal = (id: string) => {
|
||||||
|
setModalProps({
|
||||||
|
opened: true,
|
||||||
|
title: "Request Detail",
|
||||||
|
type: "input link",
|
||||||
|
detailId: id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setModalProps(defaultModalProps);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DashboardTable table={table} />
|
||||||
|
|
||||||
|
<RequestModal {...modalProps} onClose={closeModal} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
import { createColumnHelper } from "@tanstack/react-table";
|
||||||
|
import { Avatar, Badge, Flex, Stack, Text } from "@mantine/core";
|
||||||
|
import { TbEye, TbPencil, TbTrash } from "react-icons/tb";
|
||||||
|
import CrudPermissions from "@/modules/dashboard/types/CrudPermissions";
|
||||||
|
import createActionButtons from "@/modules/dashboard/utils/createActionButton";
|
||||||
|
import RequestLinkWithIssuerData from "../../types/RequestLinkWithIssuerData";
|
||||||
|
|
||||||
|
interface ColumnOptions {
|
||||||
|
permissions: Partial<CrudPermissions>;
|
||||||
|
actions: {
|
||||||
|
detail: (id: string) => void;
|
||||||
|
// edit: (id: string) => void;
|
||||||
|
// delete: (id: string) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const createColumns = (options: ColumnOptions) => {
|
||||||
|
const columnHelper = createColumnHelper<RequestLinkWithIssuerData>();
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
columnHelper.accessor("id", {
|
||||||
|
id: "sequence",
|
||||||
|
header: "#",
|
||||||
|
cell: (props) => props.row.index + 1,
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.accessor("requestedAt", {
|
||||||
|
header: "Request Date",
|
||||||
|
cell: (props) => {
|
||||||
|
const date = new Date(props.row.original.requestedAt);
|
||||||
|
return `${date.toDateString()}; ${date.toLocaleTimeString()}`;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.accessor("creator", {
|
||||||
|
header: "Issuer",
|
||||||
|
cell: (props) => {
|
||||||
|
return (<Flex gap="sm" align="center">
|
||||||
|
<Avatar src={props.cell.getValue().photoProfile} />
|
||||||
|
<div>
|
||||||
|
<Text>{props.cell.getValue().name}</Text>
|
||||||
|
<Text size="xs" c="gray">{props.cell.getValue().email}</Text>
|
||||||
|
</div>
|
||||||
|
</Flex>)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.accessor("userCount", {
|
||||||
|
header: "User Count",
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.accessor("status", {
|
||||||
|
header: "Status",
|
||||||
|
cell: (props) => {
|
||||||
|
switch (props.row.original.status) {
|
||||||
|
case "WAITING":
|
||||||
|
return <Badge color="cyan">WAITING</Badge>;
|
||||||
|
break;
|
||||||
|
case "ACCEPTED":
|
||||||
|
return <Badge color="green">ACCEPTED</Badge>;
|
||||||
|
break;
|
||||||
|
case "CANCELLED":
|
||||||
|
return <Badge color="gray">CANCELLED</Badge>;
|
||||||
|
break;
|
||||||
|
case "REJECTED":
|
||||||
|
return <Badge color="red">REJECTED</Badge>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
columnHelper.display({
|
||||||
|
id: "Actions",
|
||||||
|
header: "Actions",
|
||||||
|
cell: (props) => (
|
||||||
|
<Flex gap="xs">
|
||||||
|
{createActionButtons([
|
||||||
|
{
|
||||||
|
label: "Detail",
|
||||||
|
permission: options.permissions.read,
|
||||||
|
action: () =>
|
||||||
|
options.actions.detail(props.row.original.id),
|
||||||
|
color: "green",
|
||||||
|
icon: <TbEye />,
|
||||||
|
},
|
||||||
|
])}
|
||||||
|
</Flex>
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createColumns;
|
||||||
|
|
@ -14,21 +14,25 @@ interface Props {
|
||||||
tableData: RequestLink[];
|
tableData: RequestLink[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultModalProps: ModalProps = {
|
||||||
|
opened: false,
|
||||||
|
title: "",
|
||||||
|
type: "create",
|
||||||
|
detailId: null,
|
||||||
|
};
|
||||||
|
|
||||||
export default function RequestTable(props: Props) {
|
export default function RequestTable(props: Props) {
|
||||||
// const [modalProps, setModalProps] = useState<ModalProps>({
|
const [modalProps, setModalProps] = useState<ModalProps>(defaultModalProps);
|
||||||
// opened: false,
|
|
||||||
// title: "",
|
|
||||||
// readonly: false,
|
|
||||||
// });
|
|
||||||
const [openModal, setOpenModal] = useState(false);
|
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: props.tableData,
|
data: props.tableData,
|
||||||
columns: createColumns({
|
columns: createColumns({
|
||||||
permissions: props.permissions,
|
permissions: props.permissions,
|
||||||
actions: {
|
actions: {
|
||||||
detail: (id) => {console.log(id)}
|
detail: (id) => {
|
||||||
}
|
console.log(id);
|
||||||
|
},
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
defaultColumn: {
|
defaultColumn: {
|
||||||
|
|
@ -36,33 +40,24 @@ export default function RequestTable(props: Props) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const openFormModall = () => {
|
const openCreateModal = () => {
|
||||||
// setModalProps({
|
setModalProps({
|
||||||
// opened: true,
|
opened: true,
|
||||||
// title: "Request new link",
|
title: "Create New Office 365 Link Request",
|
||||||
// readonly: false,
|
detailId: null,
|
||||||
// });
|
type: "create",
|
||||||
// console.log('hai')
|
});
|
||||||
setOpenModal(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = () => {
|
||||||
// setModalProps({
|
setModalProps(defaultModalProps);
|
||||||
// opened: false,
|
|
||||||
// title: "",
|
|
||||||
// readonly: false,
|
|
||||||
// });
|
|
||||||
setOpenModal(false)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Flex justify="flex-end">
|
<Flex justify="flex-end">
|
||||||
{
|
{
|
||||||
<Button
|
<Button leftSection={<TbPlus />} onClick={openCreateModal}>
|
||||||
leftSection={<TbPlus />}
|
|
||||||
onClick={() => openFormModall()}
|
|
||||||
>
|
|
||||||
New Link Request
|
New Link Request
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +65,7 @@ export default function RequestTable(props: Props) {
|
||||||
|
|
||||||
<DashboardTable table={table} />
|
<DashboardTable table={table} />
|
||||||
|
|
||||||
<RequestModal opened={openModal} readonly={false} title="Create new Link Request" onClose={closeModal} />
|
<RequestModal {...modalProps} onClose={closeModal} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import createActionButtons from "@/modules/dashboard/utils/createActionButton";
|
||||||
|
|
||||||
export interface RequestLinkRow {
|
export interface RequestLinkRow {
|
||||||
id: string;
|
id: string;
|
||||||
requestDate: string,
|
requestDate: string;
|
||||||
userCount: number,
|
userCount: number;
|
||||||
status: string
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ColumnOptions {
|
interface ColumnOptions {
|
||||||
|
|
@ -34,8 +34,8 @@ const createColumns = (options: ColumnOptions) => {
|
||||||
header: "Request Date",
|
header: "Request Date",
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const date = new Date(props.row.original.requestDate);
|
const date = new Date(props.row.original.requestDate);
|
||||||
return `${date.toDateString()}; ${date.toLocaleTimeString()}`
|
return `${date.toDateString()}; ${date.toLocaleTimeString()}`;
|
||||||
}
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.accessor("userCount", {
|
columnHelper.accessor("userCount", {
|
||||||
|
|
@ -44,6 +44,22 @@ const createColumns = (options: ColumnOptions) => {
|
||||||
|
|
||||||
columnHelper.accessor("status", {
|
columnHelper.accessor("status", {
|
||||||
header: "Status",
|
header: "Status",
|
||||||
|
cell: (props) => {
|
||||||
|
switch (props.row.original.status) {
|
||||||
|
case "WAITING":
|
||||||
|
return <Badge color="cyan">WAITING</Badge>;
|
||||||
|
break;
|
||||||
|
case "ACCEPTED":
|
||||||
|
return <Badge color="green">ACCEPTED</Badge>;
|
||||||
|
break;
|
||||||
|
case "CANCELLED":
|
||||||
|
return <Badge color="gray">CANCELLED</Badge>;
|
||||||
|
break;
|
||||||
|
case "REJECTED":
|
||||||
|
return <Badge color="red">REJECTED</Badge>;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.display({
|
columnHelper.display({
|
||||||
|
|
@ -68,23 +84,13 @@ const createColumns = (options: ColumnOptions) => {
|
||||||
color: "yellow",
|
color: "yellow",
|
||||||
icon: <TbPencil />,
|
icon: <TbPencil />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
label: "Delete",
|
|
||||||
permission: options.permissions.delete,
|
|
||||||
// action: () =>
|
|
||||||
// options.actions.delete(
|
|
||||||
// props.row.original.id
|
|
||||||
// ),
|
|
||||||
color: "red",
|
|
||||||
icon: <TbTrash />,
|
|
||||||
},
|
|
||||||
])}
|
])}
|
||||||
</Flex>
|
</Flex>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
return columns;
|
return columns;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createColumns;
|
export default createColumns;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
interface RequestLinkForm {
|
interface RequestLinkForm {
|
||||||
|
id: string | undefined;
|
||||||
numberOfLinks: number;
|
numberOfLinks: number;
|
||||||
details: {
|
details: {
|
||||||
email: string;
|
email: string;
|
||||||
|
|
|
||||||
14
src/modules/resellerOffice365/types/RequestLinkWithIssuerData.d.ts
vendored
Normal file
14
src/modules/resellerOffice365/types/RequestLinkWithIssuerData.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Office365LinkRequestStatus, Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
export default interface RequestLinkWithIssuerData {
|
||||||
|
id: string;
|
||||||
|
status: Office365LinkRequestStatus;
|
||||||
|
requestedAt: Date;
|
||||||
|
creator: {
|
||||||
|
id: string;
|
||||||
|
name: string | null;
|
||||||
|
email: string | null;
|
||||||
|
photoProfile: string | null;
|
||||||
|
};
|
||||||
|
userCount: number
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user