diff --git a/README.md b/README.md index 2ce6726..b3fdb2e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ All this task must be done in 1 day - in src/pages/AdminListReceipts.jsx - there is a table listing receipts + - fix the list table, the localReceiptData need data from src/utils/data.jsx, for some reason it is not getting that data + - fix the bind issue in src/components/MkdListTable/MkdListTableBindOperations.jsx - all //TO DO the `MkdListTableV2` component has actions props which is an object of key:value pair of action definitions For example the edit action, @@ -44,6 +46,7 @@ All this task must be done in 1 day }, } + - complete the open and close action feature, update the status to open and close to see the above binding work, also not that close action is meant to show open if status is open, and open is only meant to show is status is closed - `lines -> 326,330,444,464` > the edit action binds via the bind property to a column "receipt_status", if the value of receipt_status is 1 then the edit should be hidden, i.e not shown diff --git a/src/components/ActionConfirmation/ActionConfirmation.jsx b/src/components/ActionConfirmation/ActionConfirmation.jsx new file mode 100644 index 0000000..cf120da --- /dev/null +++ b/src/components/ActionConfirmation/ActionConfirmation.jsx @@ -0,0 +1,232 @@ +import React, { useEffect } from "react"; +import { useForm } from "react-hook-form"; +import { yupResolver } from "@hookform/resolvers/yup"; +import * as yup from "yup"; +import MkdSDK from "Utils/MkdSDK"; +import { + GlobalContext, + RequestItems, + createRequest, + customRequest, + deleteRequest, + updateRequest, +} from "Context/Global"; +import { AuthContext } from "Context/Auth"; +import { MkdInput } from "Components/MkdInput"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { AddButton } from "Components/AddButton"; + +export const ActionConfirmation = ({ + data = { id: null }, + options = { endpoint: null, method: "GET" }, + onSuccess, + onClose, + multiple = false, + action = "", + mode = "create", + table = "", + inputConfirmation = true, +}) => { + let sdk = new MkdSDK(); + + const schema = yup + .object({ + confirm: yup + .string() + .required() + .oneOf([action], `Confirmation must be "${action}"`), + }) + .required(); + + const { + state: { createModel, updateModel, deleteModel }, + dispatch: globalDispatch, + } = React.useContext(GlobalContext); + const { state: authState, dispatch } = React.useContext(AuthContext); + + const { + register, + handleSubmit, + setValue, + formState: { errors }, + } = useForm({ + resolver: yupResolver(schema), + }); + + const onSubmit = async () => { + if (!multiple) { + switch (mode) { + case "create": + return createData(data); + case "update": + return editData(data); + case "delete": + return deleteData(data); + case "custom": + return customData(data); + case "manual": + return manualData(data); + } + } else { + makeRequests(); + } + }; + + const makeRequests = async () => { + const result = await createRequest(globalDispatch, dispatch, table, data); + if (!result.error && onSuccess) { + onSuccess(); + } + }; + + const editData = async (data) => { + console.log("data >>", data); + const result = await updateRequest( + globalDispatch, + dispatch, + table, + data?.id, + data + ); + + if (!result?.error && onSuccess) { + onSuccess(); + } + }; + + const createData = async (data) => { + if (action === "move") return moveRequest(data); + const result = await createRequest(globalDispatch, dispatch, table, data); + + if (!result?.error && onSuccess) { + onSuccess(); + } + }; + + const deleteData = async (data) => { + const result = await deleteRequest( + globalDispatch, + dispatch, + table, + data.id + ); + + if (!result?.error && onSuccess) { + onSuccess(); + } + }; + + const customData = async (data) => { + const result = await customRequest( + globalDispatch, + dispatch, + { + endpoint: options?.endpoint, + method: options?.method, + payload: data, + }, + RequestItems.createModel + ); + + if ( + result && + result?.hasOwnProperty("error") && + !result?.error && + onSuccess + ) { + onSuccess(result); + } + }; + const manualData = (data) => { + if (onSuccess) { + onSuccess(data); + } + }; + const moveRequest = async (data) => { + const result = await customRequest( + globalDispatch, + dispatch, + { + endpoint: "/v3/api/custom/qualitysign/inventory/move", + method: "POST", + payload: data, + }, + RequestItems.createModel + ); + + if (!result?.error && onSuccess) { + onSuccess(result); + } + }; + + React.useEffect(() => { + if (!inputConfirmation) { + setValue("confirm", action); + } + }, [inputConfirmation]); + + return ( + //
+
+
+
+
+
+ Are you sure you want to {action}{" "} + {data?.id && data?.id?.length && data?.id?.length > 1 + ? "these" + : "this"}{" "} + {table}? +
+
+
+ + Type '{action}' below +
+ } + className={"grow"} + /> +
+ +
+ onClose()} + className="grow self-end !border-soft-200 !bg-transparent font-bold !text-[#4F46E5]" + > + Cancel + + + {action} + +
+
+ +
+ ); +}; + +export default ActionConfirmation; diff --git a/src/components/ActionConfirmation/index.js b/src/components/ActionConfirmation/index.js new file mode 100644 index 0000000..b7dfa5b --- /dev/null +++ b/src/components/ActionConfirmation/index.js @@ -0,0 +1 @@ +export { default as ActionConfirmation } from "./ActionConfirmation"; diff --git a/src/components/ActionConfirmationModal/ActionConfirmationModal.jsx b/src/components/ActionConfirmationModal/ActionConfirmationModal.jsx new file mode 100644 index 0000000..9ab4572 --- /dev/null +++ b/src/components/ActionConfirmationModal/ActionConfirmationModal.jsx @@ -0,0 +1,48 @@ +import React, { useEffect } from "react"; +import { Modal } from "Components/Modal"; +import { ActionConfirmation } from "Components/ActionConfirmation"; + +export const ActionConfirmationModal = ({ + data = { id: null }, + options = { endpoint: null, method: "GET" }, + onSuccess, + onClose, + multiple = false, + action = "", + mode = "create", + table = "", + title = "", + isOpen = false, + inputConfirmation = true, + modalClasses = { + modalDialog: + "max-h-[90%] min-h-[12rem] overflow-y-auto !w-full md:!w-[29.0625rem]", + modal: "h-full", + }, +}) => { + return ( + + {isOpen && ( + + )} + + ); +}; + +export default ActionConfirmationModal; diff --git a/src/components/ActionConfirmationModal/index.js b/src/components/ActionConfirmationModal/index.js new file mode 100644 index 0000000..230f173 --- /dev/null +++ b/src/components/ActionConfirmationModal/index.js @@ -0,0 +1 @@ +export { default as ActionConfirmationModal } from "./ActionConfirmationModal"; diff --git a/src/components/AddButton/AddButton.jsx b/src/components/AddButton/AddButton.jsx index 4b07c9a..c7c3de6 100644 --- a/src/components/AddButton/AddButton.jsx +++ b/src/components/AddButton/AddButton.jsx @@ -37,7 +37,7 @@ const AddButton = ({ onClick={onClickHandle} className={`${animate && "animate-wiggle"} ${ classes.button - } relative flex h-[3rem] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-[.625rem] border border-primary bg-primary px-[.625rem] py-2 font-inter text-sm font-medium leading-loose tracking-wide text-white ${className}`} + } relative flex h-[3rem] cursor-pointer items-center justify-center gap-2 overflow-hidden rounded-[.625rem] border border-[#4F46E5] bg-[#4F46E5] px-[.625rem] py-2 font-inter text-sm font-medium leading-loose tracking-wide text-white ${className}`} > <> { + const override = { + borderColor: "#ffffff", + }; + const id = useId(); + return ( + + ); +}; + +export default memo(InteractiveButton); diff --git a/src/components/InteractiveButton/InteractiveButton.module.css b/src/components/InteractiveButton/InteractiveButton.module.css new file mode 100644 index 0000000..3925c22 --- /dev/null +++ b/src/components/InteractiveButton/InteractiveButton.module.css @@ -0,0 +1,32 @@ +.button { + position: relative; + border: none; + color: #ffffff; + text-align: center; + -webkit-transition-duration: 0.4s; /* Safari */ + transition-duration: 0.4s; + text-decoration: none; + overflow: hidden; + cursor: pointer; + /* background-color: #4F46E5; */ +} +.button:after { + content: ""; + background-color: #4f46e5; + /* background: #4f46e5; */ + display: block; + position: absolute; + padding-top: 300%; + padding-left: 350%; + margin-left: -20px !important; + margin-top: -120%; + opacity: 0; + transition: all 0.8s; +} + +.button:active:after { + padding: 0; + margin: 0; + opacity: 1; + transition: 0s; +} diff --git a/src/components/InteractiveButton/index.js b/src/components/InteractiveButton/index.js new file mode 100644 index 0000000..35724f3 --- /dev/null +++ b/src/components/InteractiveButton/index.js @@ -0,0 +1 @@ +export { default as InteractiveButton } from "./InteractiveButton"; diff --git a/src/components/MkdListTable/MkdListTableRowButtons.jsx b/src/components/MkdListTable/MkdListTableRowButtons.jsx index 5e6add0..af1ccea 100644 --- a/src/components/MkdListTable/MkdListTableRowButtons.jsx +++ b/src/components/MkdListTable/MkdListTableRowButtons.jsx @@ -36,7 +36,7 @@ const MkdListTableRowDropdown = ({ actions[key]?.action([row[actionId]]); } }} - className={`!h-[2rem] !w-[2.0713rem] !border-soft-200 !bg-white`} + className={`!h-[2rem] !w-[2.0713rem] !border-gray-200 !bg-white`} > {actions[key]?.icon ? actions[key]?.icon @@ -63,7 +63,7 @@ const MkdListTableRowDropdown = ({ // actions[key]?.action([row[actionId]]); // } }} - className={`!h-[2rem] !w-[2.0713rem] !border-soft-200 !bg-white`} + className={`!h-[2rem] !w-[2.0713rem] !border-gray-200 !bg-white`} > {actions[key]?.icon ? actions[key]?.icon diff --git a/src/components/Modal/Modal.jsx b/src/components/Modal/Modal.jsx new file mode 100644 index 0000000..129a9c9 --- /dev/null +++ b/src/components/Modal/Modal.jsx @@ -0,0 +1,89 @@ +import React, { memo, useEffect, useRef } from "react"; +import { MdClose } from "react-icons/md"; + +const Modal = ({ + children, + title, + modalCloseClick, + modalHeader, + classes = { modal: "h-full", modalDialog: "h-[90%]", modalContent: "" }, + page = "", + isOpen, +}) => { + const modalRef = useRef(null); + + // useEffect(() => { + // if (isOpen) { + // document.body.style.overflow = "hidden"; + // } else { + // document.body.style.overflow = "auto"; + // } + // }, [isOpen]); + + useEffect(() => { + const scrollableElements = document.querySelectorAll( + "body, .scrollable-container" // Add other selectors if needed + ); + + if (isOpen) { + scrollableElements.forEach((element) => { + element.style.overflow = "hidden"; + }); + } else { + scrollableElements.forEach((element) => { + element.style.overflow = "auto"; + }); + } + + return () => { + scrollableElements.forEach((element) => { + element.style.overflow = "auto"; + }); + }; + }, [isOpen]); + + return ( +
+
+ {modalHeader && ( +
+
+ {title} +
+ +
+ )} + +
+ {children} +
+
+
+ ); +}; + +const ModalMemo = memo(Modal); +export { ModalMemo as Modal }; diff --git a/src/components/Modal/ModalAlert.jsx b/src/components/Modal/ModalAlert.jsx new file mode 100644 index 0000000..a579365 --- /dev/null +++ b/src/components/Modal/ModalAlert.jsx @@ -0,0 +1,56 @@ + +// import { Close, danger, warning } from 'Assets/svgs' +import React from 'react' + + +const ModalAlert = ( { closeModalFunction, message, title, messageClasses, titleClasses, buttonText = "OK" } ) => { + return ( + + ) +} + +export default ModalAlert diff --git a/src/components/Modal/ModalPrompt.jsx b/src/components/Modal/ModalPrompt.jsx new file mode 100644 index 0000000..800ad22 --- /dev/null +++ b/src/components/Modal/ModalPrompt.jsx @@ -0,0 +1,98 @@ +import React from "react"; +import { CloseIcon, DangerIcon } from "Assets/svgs"; +import { InteractiveButton } from "Components/InteractiveButton"; +import { MdClose } from "react-icons/md"; + +// type Props = { +// closeModalFunction: () => void +// actionHandler: any +// message?: string +// title?: string +// rejectText?: string +// acceptText?: string +// titleClasses?: string +// messageClasses?: string +// } + +const ModalPrompt = ({ + open, + closeModalFunction, + actionHandler, + message, + title, + messageClasses, + titleClasses, + acceptText = "", + rejectText = "Cancel", + loading = false, + allowAccept = true, +}) => { + return ( + + ); +}; + +export default ModalPrompt; diff --git a/src/components/Modal/index.js b/src/components/Modal/index.js new file mode 100644 index 0000000..a38fc1d --- /dev/null +++ b/src/components/Modal/index.js @@ -0,0 +1,3 @@ +export { Modal } from "./Modal"; +export { default as ModalPrompt } from "./ModalPrompt"; +export { default as ModalAlert } from "./ModalAlert"; diff --git a/src/pages/AdminListReceipts.jsx b/src/pages/AdminListReceipts.jsx index 55d091f..4b0ccb5 100644 --- a/src/pages/AdminListReceipts.jsx +++ b/src/pages/AdminListReceipts.jsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import MkdSDK from "Utils/MkdSDK"; import { useNavigate } from "react-router-dom"; import { AuthContext, tokenExpireError } from "Context/Auth"; @@ -8,10 +8,16 @@ import { setGLobalProperty, } from "Context/Global"; import { MkdListTableV2 } from "Components/MkdListTable"; -import { EditIcon2, TrashIcon } from "Assets/svgs"; +import { + CircleCheckMarkIcon, + EditIcon2, + RotateIcon, + TrashIcon, +} from "Assets/svgs"; import { AiFillEye } from "react-icons/ai"; import { operations } from "Components/MkdListTable/MkdListTableBindOperations"; import { receiptData } from "Utils/data"; +import { ActionConfirmationModal } from "Components/ActionConfirmationModal"; let sdk = new MkdSDK(); @@ -309,12 +315,29 @@ const AdminListReceipts = () => { dispatch: globalDispatch, state: { confirmRequest }, } = React.useContext(GlobalContext); + const [selectedItems, setSelectedItems] = React.useState([]); + const [localReceiptData, setLocalReceiptData] = React.useState([]); + const [showOpenModal, setShowOpenModal] = React.useState(false); + const [showCloseModal, setShowCloseModal] = React.useState(false); + + const onToggleModal = (modal, toggle, ids = []) => { + switch (modal) { + case "close": + // TO DO + setSelectedItems(ids); + break; + case "open": + // TO DO + setSelectedItems(ids); + break; + } + }; return (
{ ifValue: 1, }, }, + close: { + show: true, + action: (ids) => onToggleModal("close", true, ids), + multiple: true, + locations: ["buttons"], + children: "Close", + icon: , + bind: { + column: "receipt_status", + action: "hide", + operator: "eq", + ifValue: 0, + }, + }, + open: { + show: true, + action: (ids) => onToggleModal("open", true, ids), + multiple: true, + locations: ["buttons"], + children: "Open", + icon: ( + + ), + bind: { + column: "receipt_status", + action: "hide", + operator: "eq", + ifValue: 1, + }, + }, add: { show: false, // action: () => navigate("/admin/add-receipts"), @@ -373,6 +429,47 @@ const AdminListReceipts = () => { // allowEditing refreshRef={refreshRef} /> + + onToggleModal("close", false, [])} + onSuccess={(data) => { + // TO DO - Update the receipt_status to closed here + onToggleModal("close", false, []); + }} + action="close" + mode="manual" + multiple={false} + table="receipts" + inputConfirmation={false} + /> + onToggleModal("open", false, [])} + onSuccess={(data) => { + // TO DO - Update the receipt_status to open here + onToggleModal("open", false, []); + }} + action="open" + mode="manual" + multiple={false} + table="receipts" + inputConfirmation={false} + />
); };