diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..2ba986f
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,15 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "chrome",
+ "request": "launch",
+ "name": "Launch Chrome against localhost",
+ "url": "http://localhost:8080",
+ "webRoot": "${workspaceFolder}"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/bun.lockb b/bun.lockb
index 5122c5d..6a58ffd 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/prisma/seeds/roleSeed.ts b/prisma/seeds/roleSeed.ts
index 878523a..54638d0 100644
--- a/prisma/seeds/roleSeed.ts
+++ b/prisma/seeds/roleSeed.ts
@@ -11,6 +11,12 @@ export default async function roleSeed(prisma: PrismaClient) {
description: "Has full access to the system and can manage all features and settings",
isActive: true,
name: "Super Admin"
+ },
+ {
+ code: "reseller-office-365",
+ description: "Has ability to make request Office 365 links",
+ isActive: true,
+ name: "Reseller Office 365"
}
];
diff --git a/src/app/dashboard/reseller-office-365/request/page.tsx b/src/app/dashboard/reseller-office-365/request/page.tsx
new file mode 100644
index 0000000..2db2afb
--- /dev/null
+++ b/src/app/dashboard/reseller-office-365/request/page.tsx
@@ -0,0 +1,28 @@
+import getUserRoles from "@/modules/auth/utils/getUserRoles";
+import checkMultiplePermissions from "@/modules/dashboard/services/checkMultiplePermissions";
+import checkPermission from "@/modules/dashboard/services/checkPermission";
+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();
+
+ return (
+
+ Permohonan Link Office 365
+
+
+
+
+ );
+}
diff --git a/src/modules/resellerOffice365/config.ts b/src/modules/resellerOffice365/config.ts
new file mode 100644
index 0000000..a3ef800
--- /dev/null
+++ b/src/modules/resellerOffice365/config.ts
@@ -0,0 +1,5 @@
+const resellerOffice365Config = {
+ activePeriods: ["1 Month", "1 Year", "2 Years"],
+} as const;
+
+export default resellerOffice365Config;
diff --git a/src/modules/resellerOffice365/modals/RequestModal.tsx b/src/modules/resellerOffice365/modals/RequestModal.tsx
new file mode 100644
index 0000000..6f83dce
--- /dev/null
+++ b/src/modules/resellerOffice365/modals/RequestModal.tsx
@@ -0,0 +1,194 @@
+import {
+ Button,
+ Divider,
+ Fieldset,
+ Flex,
+ Group,
+ Modal,
+ NumberInput,
+ ScrollArea,
+ Select,
+ Stack,
+ TextInput,
+} from "@mantine/core";
+import { useForm } from "@mantine/form";
+import React, { useState } from "react";
+import {
+ TbAt,
+ TbCalendarTime,
+ TbDeviceFloppy,
+ TbLink,
+ TbUser,
+ TbUsers,
+} from "react-icons/tb";
+import resellerOffice365Config from "../config";
+
+export interface ModalProps {
+ title: string;
+ opened: boolean;
+ readonly: boolean;
+ onClose?: () => void;
+}
+
+interface FormType {
+ numberOfLinks: number;
+ details: {
+ email: string;
+ activePeriod: (typeof resellerOffice365Config.activePeriods)[number];
+ endUserQty: number;
+ }[];
+}
+
+export default function RequestModal(props: ModalProps) {
+ const [isSubmitting, setSubmitting] = useState(false);
+ const [formState, setFormState] = useState<
+ "idle" | "submitting" | "waiting"
+ >("idle");
+
+ const closeModal = () => {
+ props.onClose ? props.onClose() : "";
+ };
+
+ const form = useForm({
+ initialValues: {
+ numberOfLinks: 1,
+ details: [
+ {
+ email: "",
+ activePeriod: "2 Years",
+ endUserQty: 1,
+ },
+ ],
+ },
+ onValuesChange: (values, prev) => {
+ // Check if numberOfLinks has changed
+ if (values.numberOfLinks !== prev.numberOfLinks) {
+ const currentDetails = values.details;
+ const targetLength = values.numberOfLinks;
+
+ // Add new detail objects if numberOfLinks has increased
+ while (currentDetails.length < targetLength) {
+ currentDetails.push({
+ email: "",
+ activePeriod: "2 Years",
+ endUserQty: 1,
+ });
+ }
+
+ // Remove extra detail objects if numberOfLinks has decreased
+ if (currentDetails.length > targetLength) {
+ currentDetails.length = targetLength; // Adjusts the array length
+ }
+
+ // Update the form values with the adjusted details array
+ form.setValues({
+ ...values,
+ details: currentDetails,
+ });
+ }
+ },
+ });
+
+ const disableChange = formState !== "idle";
+
+ const handleSubmit = (values: FormType) => {
+ const submitableState = ["idle"];
+
+ if (!submitableState.includes(formState)) return; //prevent submit
+
+ setFormState("submitting");
+ };
+
+ return (
+
+
+
+ );
+}
diff --git a/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx b/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx
new file mode 100644
index 0000000..b8f05ea
--- /dev/null
+++ b/src/modules/resellerOffice365/tables/RequestTable/RequestTable.tsx
@@ -0,0 +1,76 @@
+"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";
+
+interface Props {
+ permissions: Partial;
+ tableData: RequestLink[];
+}
+
+export default function RequestTable(props: Props) {
+ // const [modalProps, setModalProps] = useState({
+ // opened: false,
+ // title: "",
+ // readonly: false,
+ // });
+ const [openModal, setOpenModal] = useState(false);
+
+ const table = useReactTable({
+ data: props.tableData,
+ columns: createColumns({
+ permissions: props.permissions,
+ actions: {
+ detail: (id) => {console.log(id)}
+ }
+ }),
+ getCoreRowModel: getCoreRowModel(),
+ defaultColumn: {
+ cell: (props) => {props.getValue() as ReactNode},
+ },
+ });
+
+ const openFormModall = () => {
+ // setModalProps({
+ // opened: true,
+ // title: "Request new link",
+ // readonly: false,
+ // });
+ // console.log('hai')
+ setOpenModal(true);
+ };
+
+ const closeModal = () => {
+ // setModalProps({
+ // opened: false,
+ // title: "",
+ // readonly: false,
+ // });
+ setOpenModal(false)
+ };
+
+ return (
+ <>
+
+ {
+ }
+ onClick={() => openFormModall()}
+ >
+ New Link Request
+
+ }
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/modules/resellerOffice365/tables/RequestTable/columns.tsx b/src/modules/resellerOffice365/tables/RequestTable/columns.tsx
new file mode 100644
index 0000000..d75909c
--- /dev/null
+++ b/src/modules/resellerOffice365/tables/RequestTable/columns.tsx
@@ -0,0 +1,86 @@
+import { createColumnHelper } from "@tanstack/react-table";
+import { Badge, Flex } 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";
+
+export interface RequestLinkRow {
+ id: string;
+ requestDate: Date,
+ userCount: number,
+ status: string
+}
+
+interface ColumnOptions {
+ permissions: Partial;
+ actions: {
+ detail: (id: string) => void;
+ // edit: (id: string) => void;
+ // delete: (id: string) => void;
+ };
+}
+
+const createColumns = (options: ColumnOptions) => {
+ const columnHelper = createColumnHelper();
+
+ const columns = [
+ columnHelper.accessor("id", {
+ id: "sequence",
+ header: "#",
+ cell: (props) => props.row.index + 1,
+ }),
+
+ columnHelper.accessor("requestDate", {
+ header: "Request Date",
+ }),
+
+ columnHelper.accessor("userCount", {
+ header: "User Count",
+ }),
+
+ columnHelper.accessor("status", {
+ header: "Status",
+ }),
+
+ columnHelper.display({
+ id: "Actions",
+ header: "Actions",
+ cell: (props) => (
+
+ {createActionButtons([
+ {
+ label: "Detail",
+ permission: options.permissions.read,
+ action: () =>
+ options.actions.detail(props.row.original.id),
+ color: "green",
+ icon: ,
+ },
+ {
+ label: "Edit",
+ permission: options.permissions.update,
+ // action: () =>
+ // options.actions.edit(props.row.original.id),
+ color: "yellow",
+ icon: ,
+ },
+ {
+ label: "Delete",
+ permission: options.permissions.delete,
+ // action: () =>
+ // options.actions.delete(
+ // props.row.original.id
+ // ),
+ color: "red",
+ icon: ,
+ },
+ ])}
+
+ ),
+ }),
+ ];
+
+ return columns;
+};
+
+export default createColumns;
diff --git a/src/modules/resellerOffice365/types/RequestLink.d.ts b/src/modules/resellerOffice365/types/RequestLink.d.ts
new file mode 100644
index 0000000..e56d7e0
--- /dev/null
+++ b/src/modules/resellerOffice365/types/RequestLink.d.ts
@@ -0,0 +1,6 @@
+export default interface RequestLink {
+ id: string;
+ requestDate: Date,
+ userCount: number,
+ status: string
+}
\ No newline at end of file