Compare commits
15 Commits
cc3943196e
...
962102d44c
| Author | SHA1 | Date | |
|---|---|---|---|
| 962102d44c | |||
| 03c4988ff1 | |||
| 27f4597348 | |||
| 265d0b2209 | |||
| a56f643301 | |||
| 8b34a69dca | |||
| f7fea99f30 | |||
| bd461995c7 | |||
| 17681e64ad | |||
| 154eba0474 | |||
| dea0047007 | |||
| f4b7027708 | |||
| b4b193a9fe | |||
| 9d33cb5372 | |||
| 48b9b70c4a |
@@ -25,7 +25,7 @@ function App() {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
return (
|
return (
|
||||||
<LoadingProvider>
|
<LoadingProvider>
|
||||||
<div className="flex flex-col min-h-screen">
|
<div className="flex flex-col min-h-screen scrollbar">
|
||||||
<Header />
|
<Header />
|
||||||
<LoadingBarWrapper />
|
<LoadingBarWrapper />
|
||||||
<main className="overflow-hidden flex flex-grow transition-color-mode md:p-10 2xl:px-70 bg-surface dark:bg-surface-dark">
|
<main className="overflow-hidden flex flex-grow transition-color-mode md:p-10 2xl:px-70 bg-surface dark:bg-surface-dark">
|
||||||
@@ -34,9 +34,9 @@ function App() {
|
|||||||
<motion.div
|
<motion.div
|
||||||
className="w-full ovwerflow-hidden"
|
className="w-full ovwerflow-hidden"
|
||||||
key={location.pathname}
|
key={location.pathname}
|
||||||
initial={{ opacity: 0, x: 50 }}
|
initial={{ y: 15, opacity: 0 }}
|
||||||
animate={{ opacity: 1, x: 0 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
exit={{ opacity: 0, x: 50 }}
|
exit={{ y: 15, opacity: 0 }}
|
||||||
>
|
>
|
||||||
<AnimatedOutlet />
|
<AnimatedOutlet />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function Button({text, onClick, disabled}) {
|
|||||||
<motion.button
|
<motion.button
|
||||||
whileHover={!disabled && { scale: 1.05 }}
|
whileHover={!disabled && { scale: 1.05 }}
|
||||||
whileTap={!disabled && { scale: 0.95 }}
|
whileTap={!disabled && { scale: 0.95 }}
|
||||||
className={clsx("px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark", disabled && "bg-[#ccc] dark:bg-[#ccc]")}
|
className={clsx("px-12 py-2 text-lg text-white dark:text-primary-dark rounded-full bg-primary dark:bg-secondary-dark", disabled && "bg-[#cccccc] dark:bg-[#cccccc]")}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,22 +1,38 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { motion, AnimatePresence } from "motion/react";
|
||||||
function FormInput({
|
function FormInput({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
maxLength=17,
|
valid = true,
|
||||||
|
maxLength = 17,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
className = "",
|
className = "",
|
||||||
type = "text",
|
type = "text",
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<input
|
<div>
|
||||||
readOnly={readOnly}
|
<input
|
||||||
value={value}
|
readOnly={readOnly}
|
||||||
className={`w-1/2 md:w-1/3 lg:w-1/4 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`}
|
value={value}
|
||||||
onChange={onChange}
|
className={`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`}
|
||||||
type={type}
|
onChange={onChange}
|
||||||
maxLength={maxLength}
|
type={type}
|
||||||
/>
|
maxLength={maxLength}
|
||||||
|
/>
|
||||||
|
<AnimatePresence>
|
||||||
|
{!valid && (
|
||||||
|
<motion.div
|
||||||
|
className="text-sm text-error ml-3 pt-1"
|
||||||
|
initial={{ y: 15, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
exit={{ y: 15, opacity: 0 }}
|
||||||
|
key="cabinetIdError"
|
||||||
|
>
|
||||||
|
Invalid Value
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,6 +43,7 @@ FormInput.propTypes = {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
maxLength: PropTypes.number,
|
maxLength: PropTypes.number,
|
||||||
|
valid: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FormInput;
|
export default FormInput;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { motion, AnimatePresence } from "motion/react";
|
||||||
|
|
||||||
function FormSelect({ value, onChange, options, className }) {
|
function FormSelect({ value, onChange, options, className, valid = true }) {
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
<select
|
<select
|
||||||
value={value}
|
value={value}
|
||||||
className={
|
className={
|
||||||
"w-1/2 md:w-1/3 lg:w-1/4 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey " +
|
"w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey " +
|
||||||
className
|
className
|
||||||
}
|
}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
@@ -19,6 +21,20 @@ function FormSelect({ value, onChange, options, className }) {
|
|||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
|
<AnimatePresence>
|
||||||
|
{!valid && (
|
||||||
|
<motion.div
|
||||||
|
className="text-sm text-error ml-3 pt-1"
|
||||||
|
initial={{ y: 15, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
exit={{ y: 15, opacity: 0 }}
|
||||||
|
key="cabinetIdError"
|
||||||
|
>
|
||||||
|
Invalid Value
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,6 +42,7 @@ FormSelect.propTypes = {
|
|||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
|
valid: PropTypes.bool,
|
||||||
options: PropTypes.arrayOf(
|
options: PropTypes.arrayOf(
|
||||||
PropTypes.shape({
|
PropTypes.shape({
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
|
|||||||
48
src/components/Notification.jsx
Normal file
48
src/components/Notification.jsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import clsx from "clsx";
|
||||||
|
import { Copy } from "lucide-react";
|
||||||
|
import { motion } from "motion/react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
function Notification({ message, type }) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ y: 15, opacity: 0 }}
|
||||||
|
animate={{ y: 0, opacity: 1 }}
|
||||||
|
exit={{ y: 15, opacity: 0 }}
|
||||||
|
className={clsx(
|
||||||
|
"p-2 pl-8 mb-8 font-body text-lg border-2 rounded-3xl flex items-center gap-2",
|
||||||
|
type === "error"
|
||||||
|
? "bg-error-surface text-white border-error"
|
||||||
|
: type === "success"
|
||||||
|
? "bg-success text-white border-green-700"
|
||||||
|
: type === "warning"
|
||||||
|
? "bg-warning-surface text-white border-warning"
|
||||||
|
: ""
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{message.split(":").map((msg, index) => {
|
||||||
|
return index === 1 ? (
|
||||||
|
<span key={index} className="border-b border-dashed">
|
||||||
|
{msg}
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span key={index}>{msg}</span>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{message.split(":")[1] && (
|
||||||
|
<Copy
|
||||||
|
cursor={"pointer"}
|
||||||
|
size={15}
|
||||||
|
onClick={navigator.clipboard.writeText(message.split(":")[1].trim())}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Notification.propTypes = {
|
||||||
|
message: PropTypes.string.isRequired,
|
||||||
|
type: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification;
|
||||||
10
src/main.jsx
10
src/main.jsx
@@ -14,8 +14,8 @@ import LockerStatus from "./pages/LockerStatus.jsx";
|
|||||||
import KeySwap from "./pages/KeySwap.jsx";
|
import KeySwap from "./pages/KeySwap.jsx";
|
||||||
import ChargeManagement from "./pages/ChargeManagement.jsx";
|
import ChargeManagement from "./pages/ChargeManagement.jsx";
|
||||||
import ChargeEdit from "./pages/ChargeEdit.jsx";
|
import ChargeEdit from "./pages/ChargeEdit.jsx";
|
||||||
import Placeholder from "./pages/Placeholder.jsx";
|
import CheckInOutManagement from "./pages/CheckInOutManagement.jsx";
|
||||||
|
import CheckInOutLog from "./pages/CheckInOutLog.jsx";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -64,7 +64,11 @@ const router = createBrowserRouter([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "operation/check-in-out",
|
path: "operation/check-in-out",
|
||||||
element: <Placeholder />
|
element: <CheckInOutManagement />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "operation/check-in-out/log",
|
||||||
|
element: <CheckInOutLog />
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ import FormField from "../components/FormField";
|
|||||||
import FormInput from "../components/FormInput";
|
import FormInput from "../components/FormInput";
|
||||||
import FormSelect from "../components/FormSelect";
|
import FormSelect from "../components/FormSelect";
|
||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import { useToast } from "../hooks/useToast";
|
|
||||||
import productInfo from "../util/productList";
|
import productInfo from "../util/productList";
|
||||||
import ProductListTable from "../components/ProductListTable";
|
import ProductListTable from "../components/ProductListTable";
|
||||||
|
|
||||||
function AccountCreation() {
|
function AccountCreation() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const showToast = useToast();
|
|
||||||
const [notification] = useState({
|
const [notification] = useState({
|
||||||
visible: false,
|
visible: false,
|
||||||
message: "",
|
message: "",
|
||||||
@@ -179,7 +177,6 @@ function AccountCreation() {
|
|||||||
setAccountDetails(newValidationState);
|
setAccountDetails(newValidationState);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
showToast("Highlighted fields are invalid", "error");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("Form is valid", accountDetails);
|
console.log("Form is valid", accountDetails);
|
||||||
@@ -196,9 +193,9 @@ function AccountCreation() {
|
|||||||
className="fixed z-50 inset-0 flex items-center justify-center bg-black/50"
|
className="fixed z-50 inset-0 flex items-center justify-center bg-black/50"
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0.8 }}
|
initial={{ y: 15, opacity: 0 }}
|
||||||
animate={{ scale: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
exit={{ scale: 0.8 }}
|
exit={{ y: 15, opacity: 0 }}
|
||||||
className="flex flex-col items-center bg-white p-4 py-8 rounded-3xl w-[60%] max-h-[80%] overflow-auto font-body"
|
className="flex flex-col items-center bg-white p-4 py-8 rounded-3xl w-[60%] max-h-[80%] overflow-auto font-body"
|
||||||
>
|
>
|
||||||
<h2 className="text-xl mb-4">Select Product</h2>
|
<h2 className="text-xl mb-4">Select Product</h2>
|
||||||
@@ -216,6 +213,7 @@ function AccountCreation() {
|
|||||||
const renerField = (field) => {
|
const renerField = (field) => {
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
value: accountDetails[field.name],
|
value: accountDetails[field.name],
|
||||||
|
valid: accountDetails[`${field.name}Valid`],
|
||||||
onChange: (e) => {
|
onChange: (e) => {
|
||||||
const newAccountDetails = { ...accountDetails };
|
const newAccountDetails = { ...accountDetails };
|
||||||
newAccountDetails[field.name] = e.target.value;
|
newAccountDetails[field.name] = e.target.value;
|
||||||
@@ -246,9 +244,9 @@ function AccountCreation() {
|
|||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{notification.visible && (
|
{notification.visible && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0 }}
|
initial={{ y: 15, opacity: 0 }}
|
||||||
animate={{ opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
exit={{ opacity: 0 }}
|
exit={{ y: 15, opacity: 0 }}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
"p-4 mb-8 font-body text-center text-xl rounded-2xl flex items-center justify-center gap-2",
|
"p-4 mb-8 font-body text-center text-xl rounded-2xl flex items-center justify-center gap-2",
|
||||||
notification.type === "error"
|
notification.type === "error"
|
||||||
|
|||||||
@@ -36,103 +36,99 @@ function CabinetCreation() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<motion.div className="w-full h-fit" initial={{ scale: 0.9 }} animate={{ scale: 1 }} exit={{ scale: 0 }}>
|
<FormBox title={t("cabinetCreation")}>
|
||||||
<FormBox title={t("cabinetCreation")}>
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
<div className="flex">
|
||||||
<div className="flex">
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
|
||||||
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
|
{t("cabinetId")}
|
||||||
{t("cabinetId")}
|
</label>
|
||||||
</label>
|
<div className="w-full">
|
||||||
<div className="w-full">
|
<input
|
||||||
<input
|
value={cabinetId.id}
|
||||||
value={cabinetId.id}
|
className={clsx(
|
||||||
className={clsx(
|
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||||
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
!cabinetId.valid && "border-error"
|
||||||
!cabinetId.valid && "border-error"
|
|
||||||
)}
|
|
||||||
onChange={(e) =>
|
|
||||||
setCabinetId({
|
|
||||||
id: e.target.value.toUpperCase(),
|
|
||||||
valid: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
type="text"
|
|
||||||
maxLength={6}
|
|
||||||
/>
|
|
||||||
<AnimatePresence initial={false}>
|
|
||||||
{!cabinetId.valid && (
|
|
||||||
<motion.div
|
|
||||||
className="w-1/5 text-sm text-error ml-3 pt-1"
|
|
||||||
initial={{ opacity: 0,scale: 0 }}
|
|
||||||
animate={{ opacity: 1,scale: 1 }}
|
|
||||||
exit={{ opacity: 0,scale: 0 }}
|
|
||||||
key="cabinetIdError"
|
|
||||||
>
|
|
||||||
Invalid Cabinet Id
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex">
|
|
||||||
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
|
|
||||||
{t("cabinetKeyId")}
|
|
||||||
</label>
|
|
||||||
<div className="w-full">
|
|
||||||
<input
|
|
||||||
value={cabinetKeyId.id}
|
|
||||||
className={clsx(
|
|
||||||
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
|
||||||
!cabinetKeyId.valid && "border-error"
|
|
||||||
)}
|
|
||||||
onChange={(e) =>
|
|
||||||
setCabinetKeyId({
|
|
||||||
id: e.target.value.toUpperCase(),
|
|
||||||
valid: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
type="text"
|
|
||||||
maxLength={6}
|
|
||||||
/>
|
|
||||||
{!cabinetKeyId.valid && (
|
|
||||||
<div className="text-sm text-error ml-3 pt-1">
|
|
||||||
Invalid Key Id
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
onChange={(e) =>
|
||||||
</div>
|
setCabinetId({
|
||||||
<div className="flex">
|
id: e.target.value.toUpperCase(),
|
||||||
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
|
valid: true,
|
||||||
{t("noOfLockers")}
|
})
|
||||||
</label>
|
}
|
||||||
<div className="w-full">
|
type="text"
|
||||||
<input
|
maxLength={6}
|
||||||
value={noOfLockers.number}
|
/>
|
||||||
className={clsx(
|
<AnimatePresence initial={false}>
|
||||||
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
{!cabinetId.valid && (
|
||||||
!noOfLockers.valid && "border-error"
|
<motion.div
|
||||||
)}
|
className="w-1/5 text-sm text-error ml-3 pt-1"
|
||||||
onChange={(e) =>
|
initial={{ opacity: 0, scale: 0 }}
|
||||||
setNoOfLockers({ number: e.target.value, valid: true })
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
}
|
exit={{ opacity: 0, scale: 0 }}
|
||||||
type="number"
|
key="cabinetIdError"
|
||||||
/>
|
>
|
||||||
{!noOfLockers.valid && (
|
Invalid Cabinet Id
|
||||||
<div className="text-sm text-error ml-3 pt-1">
|
</motion.div>
|
||||||
Invalid Number of Lockers
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<div className="flex">
|
||||||
text={t("next")}
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
|
||||||
onClick={(e) => {
|
{t("cabinetKeyId")}
|
||||||
handleNext(e);
|
</label>
|
||||||
}}
|
<div className="w-full">
|
||||||
/>
|
<input
|
||||||
</FormBox>
|
value={cabinetKeyId.id}
|
||||||
</motion.div>
|
className={clsx(
|
||||||
|
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||||
|
!cabinetKeyId.valid && "border-error"
|
||||||
|
)}
|
||||||
|
onChange={(e) =>
|
||||||
|
setCabinetKeyId({
|
||||||
|
id: e.target.value.toUpperCase(),
|
||||||
|
valid: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
type="text"
|
||||||
|
maxLength={6}
|
||||||
|
/>
|
||||||
|
{!cabinetKeyId.valid && (
|
||||||
|
<div className="text-sm text-error ml-3 pt-1">Invalid Key Id</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[15%]">
|
||||||
|
{t("noOfLockers")}
|
||||||
|
</label>
|
||||||
|
<div className="w-full">
|
||||||
|
<input
|
||||||
|
value={noOfLockers.number}
|
||||||
|
className={clsx(
|
||||||
|
"w-1/5 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 border-grey text-grey focus:outline-grey",
|
||||||
|
!noOfLockers.valid && "border-error"
|
||||||
|
)}
|
||||||
|
onChange={(e) =>
|
||||||
|
setNoOfLockers({ number: e.target.value, valid: true })
|
||||||
|
}
|
||||||
|
type="number"
|
||||||
|
/>
|
||||||
|
{!noOfLockers.valid && (
|
||||||
|
<div className="text-sm text-error ml-3 pt-1">
|
||||||
|
Invalid Number of Lockers
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
text={t("next")}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleNext(e);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormBox>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ function CabinetMaintenace() {
|
|||||||
<FormBox title={t("cabinetMaintenance")}>
|
<FormBox title={t("cabinetMaintenance")}>
|
||||||
<div className="p-2 pt-7">
|
<div className="p-2 pt-7">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
|
||||||
{t("operation")}
|
{t("operation")}
|
||||||
</label>
|
</label>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -45,13 +45,13 @@ function CabinetMaintenace() {
|
|||||||
</option>
|
</option>
|
||||||
<option value="create">{t("create")}</option>
|
<option value="create">{t("create")}</option>
|
||||||
</select>
|
</select>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence>
|
||||||
{!operation.valid && (
|
{!operation.valid && (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-1/5 text-sm text-error ml-3 pt-1"
|
className="w-1/5 text-sm text-error ml-3 pt-1"
|
||||||
initial={{ opacity: 0,scale: 0 }}
|
initial={{ y: 15, opacity: 0 }}
|
||||||
animate={{ opacity: 1,scale: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
exit={{ opacity: 0,scale: 0 }}
|
exit={{ y: 15, opacity: 0 }}
|
||||||
key="cabinetIdError"
|
key="cabinetIdError"
|
||||||
>
|
>
|
||||||
Invalid Operation
|
Invalid Operation
|
||||||
@@ -60,7 +60,6 @@ function CabinetMaintenace() {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<Button text={t("next")} onClick={(e) => handleNext(e)} />
|
<Button text={t("next")} onClick={(e) => handleNext(e)} />
|
||||||
</FormBox>
|
</FormBox>
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import FormSelect from "../components/FormSelect";
|
|||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import FormBox from "../components/FormBox";
|
import FormBox from "../components/FormBox";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useToast } from "../hooks/useToast";
|
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { lockerService } from "../services/locker.service";
|
import { lockerService } from "../services/locker.service";
|
||||||
import clsx from "clsx";
|
import { AnimatePresence } from "motion/react";
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
|
||||||
import { Pencil } from "lucide-react";
|
import { Pencil } from "lucide-react";
|
||||||
|
import Notification from "../components/Notification";
|
||||||
|
|
||||||
function ChargeEdit() {
|
function ChargeEdit() {
|
||||||
const [chargeDetails, setChargeDetails] = useState({
|
const [chargeDetails, setChargeDetails] = useState({
|
||||||
@@ -21,23 +20,22 @@ function ChargeEdit() {
|
|||||||
penaltyAmountEdit: false,
|
penaltyAmountEdit: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [notification, setNotification] = useState({
|
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||||
visible: false,
|
|
||||||
message: "",
|
|
||||||
type: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const { setIsLoading } = useLoading();
|
const { setIsLoading } = useLoading();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const showToast = useToast();
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { productCode, interestCategory } = location.state;
|
const productCode = location.state?.productCode;
|
||||||
|
const interestCategory = location.state?.interestCategory;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCharges = async () => {
|
const fetchCharges = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const response = await lockerService.getCharges(productCode, interestCategory);
|
const response = await lockerService.getCharges(
|
||||||
|
productCode,
|
||||||
|
interestCategory
|
||||||
|
);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
const { rent, penalty } = response.data;
|
const { rent, penalty } = response.data;
|
||||||
setChargeDetails({
|
setChargeDetails({
|
||||||
@@ -48,7 +46,6 @@ function ChargeEdit() {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
@@ -56,7 +53,6 @@ function ChargeEdit() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: error.message,
|
message: error.message,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
@@ -67,6 +63,10 @@ function ChargeEdit() {
|
|||||||
fetchCharges();
|
fetchCharges();
|
||||||
}, [productCode, interestCategory, setIsLoading]);
|
}, [productCode, interestCategory, setIsLoading]);
|
||||||
|
|
||||||
|
if (!location.state) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
const formFields = [
|
const formFields = [
|
||||||
{
|
{
|
||||||
name: "rentAmount",
|
name: "rentAmount",
|
||||||
@@ -75,10 +75,12 @@ function ChargeEdit() {
|
|||||||
subType: "number",
|
subType: "number",
|
||||||
readOnly: !chargeDetails.rentAmountEdit,
|
readOnly: !chargeDetails.rentAmountEdit,
|
||||||
icon: {
|
icon: {
|
||||||
icon: <Pencil size={22}/>,
|
icon: <Pencil size={22} />,
|
||||||
mode: "plain",
|
mode: "plain",
|
||||||
onClick: () => {setChargeDetails({...chargeDetails, rentAmountEdit: true})},
|
onClick: () => {
|
||||||
}
|
setChargeDetails({ ...chargeDetails, rentAmountEdit: true });
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "penaltyAmount",
|
name: "penaltyAmount",
|
||||||
@@ -87,20 +89,18 @@ function ChargeEdit() {
|
|||||||
subType: "number",
|
subType: "number",
|
||||||
readOnly: !chargeDetails.penaltyAmountEdit,
|
readOnly: !chargeDetails.penaltyAmountEdit,
|
||||||
icon: {
|
icon: {
|
||||||
icon: <Pencil size={22}/>,
|
icon: <Pencil size={22} />,
|
||||||
mode: "plain",
|
mode: "plain",
|
||||||
onClick: () => {setChargeDetails({...chargeDetails, penaltyAmountEdit: true})},
|
onClick: () => {
|
||||||
}
|
setChargeDetails({ ...chargeDetails, penaltyAmountEdit: true });
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
if(!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit) {
|
|
||||||
showToast("No changes made", "warning");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const response = await lockerService.updateCharges(
|
const response = await lockerService.updateCharges(
|
||||||
@@ -111,13 +111,11 @@ function ChargeEdit() {
|
|||||||
);
|
);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
@@ -125,7 +123,6 @@ function ChargeEdit() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: error.message,
|
message: error.message,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
@@ -164,21 +161,7 @@ function ChargeEdit() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{notification.visible && (
|
{notification.message !== "" && <Notification {...notification} />}
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className={clsx(
|
|
||||||
"p-4 mb-8 font-body text-center text-lg 2xl:text-xl rounded-2xl flex items-center justify-center gap-2",
|
|
||||||
notification.type === "error"
|
|
||||||
? "bg-error-surface text-error"
|
|
||||||
: "bg-success-surface text-success"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{notification.message}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{notification.type === "success" && (
|
{notification.type === "success" && (
|
||||||
@@ -188,7 +171,13 @@ function ChargeEdit() {
|
|||||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
{formFields.map(renderField)}
|
{formFields.map(renderField)}
|
||||||
</div>
|
</div>
|
||||||
<Button text={t("submit")} onClick={handleSubmit} disabled={!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit}/>
|
<Button
|
||||||
|
text={t("submit")}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={
|
||||||
|
!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit
|
||||||
|
}
|
||||||
|
/>
|
||||||
</FormBox>
|
</FormBox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ function ChargeManagement() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormBox title={t("lockerStatus")}>
|
<FormBox title={t("chargeManagement")}>
|
||||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
{formFields.map(renderField)}
|
{formFields.map(renderField)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
90
src/pages/CheckInOutLog.jsx
Normal file
90
src/pages/CheckInOutLog.jsx
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import FormBox from "../components/FormBox";
|
||||||
|
import Button from "../components/Button";
|
||||||
|
import Notification from "../components/Notification";
|
||||||
|
import { lockerService } from "../services/locker.service";
|
||||||
|
import { useToast } from "../hooks/useToast";
|
||||||
|
import { useLoading } from "../hooks/useLoading";
|
||||||
|
|
||||||
|
function CheckInOutLog() {
|
||||||
|
const [time, setTime] = useState(null);
|
||||||
|
const [checkType, setCheckType] = useState("");
|
||||||
|
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||||
|
|
||||||
|
const location = useLocation();
|
||||||
|
const showToast = useToast();
|
||||||
|
const { setIsLoading } = useLoading();
|
||||||
|
const accountNumber = location.state?.accountNumber;
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (time === null || checkType === "") {
|
||||||
|
showToast("Please fill in all fields", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Add your logic here
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const response = await lockerService.checkInOut(
|
||||||
|
accountNumber,
|
||||||
|
time,
|
||||||
|
checkType
|
||||||
|
);
|
||||||
|
if (response.status === 200) {
|
||||||
|
setNotification({ message: response.data.message, type: "success" });
|
||||||
|
} else {
|
||||||
|
console.log(response);
|
||||||
|
setNotification({ message: response.data.message, type: "error" });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
setNotification({ message: error.message, type: "error" });
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{notification.message !== "" && <Notification {...notification} />}
|
||||||
|
<FormBox title="Check In/Out Log">
|
||||||
|
<div className="px-4 pt-7 text-2xl font-display font-bold text-primary dark:text-primary-dark">
|
||||||
|
{accountNumber}
|
||||||
|
</div>
|
||||||
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
|
<div className="flex">
|
||||||
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
|
||||||
|
Time
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
className="w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey text-black"
|
||||||
|
onChange={(e) => setTime(e.target.value)}
|
||||||
|
value={time}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex">
|
||||||
|
<label className="mr-4 text-lg text-black dark:text-primary-dark w-[10%]">
|
||||||
|
Check Type
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
className="w-1/5 h-10 px-2 rounded-full dark:bg-grey dark:text-primary-dark border-2 focus:outline-grey border-grey"
|
||||||
|
onChange={(e) => setCheckType(e.target.value)}
|
||||||
|
value={checkType}
|
||||||
|
>
|
||||||
|
<option value="" disabled>
|
||||||
|
Select
|
||||||
|
</option>
|
||||||
|
<option value="check-in">Check In</option>
|
||||||
|
<option value="check-out">Check Out</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button text="Submit" onClick={handleSubmit} />
|
||||||
|
</FormBox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CheckInOutLog;
|
||||||
87
src/pages/CheckInOutManagement.jsx
Normal file
87
src/pages/CheckInOutManagement.jsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import FormBox from "../components/FormBox";
|
||||||
|
import { useState } from "react";
|
||||||
|
import FormField from "../components/FormField";
|
||||||
|
import FormInput from "../components/FormInput";
|
||||||
|
import { Search } from "lucide-react";
|
||||||
|
import Button from "../components/Button";
|
||||||
|
import { AnimatePresence } from "motion/react";
|
||||||
|
import Notification from "../components/Notification";
|
||||||
|
import { useToast } from "../hooks/useToast";
|
||||||
|
import { lockerService } from "../services/locker.service";
|
||||||
|
import { useLoading } from "../hooks/useLoading";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
function CheckInOutManagement() {
|
||||||
|
const [accountNumber, setAccountNumber] = useState("");
|
||||||
|
const [notification, setNotification] = useState({
|
||||||
|
visible: false,
|
||||||
|
message: "",
|
||||||
|
type: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const showToast = useToast();
|
||||||
|
const { setIsLoading } = useLoading();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleNext = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (accountNumber === "") {
|
||||||
|
showToast("Account Number is required", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const response = await lockerService.preCheckIn(accountNumber);
|
||||||
|
console.log(response.data);
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.data;
|
||||||
|
if (data.code === 1) {
|
||||||
|
navigate("log", { state: { accountNumber } });
|
||||||
|
} else if (data.code === 2) {
|
||||||
|
setNotification({
|
||||||
|
visible: true,
|
||||||
|
message:
|
||||||
|
"Monthly access limit exceeded. A fine will be charged for each additional access.",
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
} else if (data.code === 3) {
|
||||||
|
setNotification({
|
||||||
|
visible: true,
|
||||||
|
message:
|
||||||
|
"Rent for this account is due. Please pay the rent amount in full to access the locker.",
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
setNotification(error.message, "error");
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AnimatePresence>
|
||||||
|
{notification.visible && <Notification notification={notification} />}
|
||||||
|
</AnimatePresence>
|
||||||
|
<FormBox title="Check In/Out">
|
||||||
|
<div className="p-2 pt-7">
|
||||||
|
<FormField
|
||||||
|
label="Account Number"
|
||||||
|
icon={{ icon: <Search size={17} />, onClick: () => {} }}
|
||||||
|
>
|
||||||
|
<FormInput
|
||||||
|
type="text"
|
||||||
|
value={accountNumber}
|
||||||
|
onChange={(e) => setAccountNumber(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
</div>
|
||||||
|
<Button text="Next" onClick={handleNext} />
|
||||||
|
</FormBox>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CheckInOutManagement;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { motion, AnimatePresence } from "motion/react";
|
import { AnimatePresence } from "motion/react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useToast } from "../hooks/useToast";
|
import { useToast } from "../hooks/useToast";
|
||||||
import { useLoading } from "../hooks/useLoading";
|
import { useLoading } from "../hooks/useLoading";
|
||||||
@@ -10,17 +10,13 @@ import Button from "../components/Button";
|
|||||||
import { lockerService } from "../services/locker.service";
|
import { lockerService } from "../services/locker.service";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import FormBox from "../components/FormBox";
|
import FormBox from "../components/FormBox";
|
||||||
import { Copy } from "lucide-react";
|
import Notification from "../components/Notification";
|
||||||
|
|
||||||
function KeySwap() {
|
function KeySwap() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const showToast = useToast();
|
const showToast = useToast();
|
||||||
const { isLoading, setIsLoading } = useLoading();
|
const { isLoading, setIsLoading } = useLoading();
|
||||||
const [notification, setNotification] = useState({
|
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||||
visible: false,
|
|
||||||
message: "",
|
|
||||||
type: "",
|
|
||||||
});
|
|
||||||
const [keySwapDetails, setKeySwapDetails] = useState({
|
const [keySwapDetails, setKeySwapDetails] = useState({
|
||||||
cabinetId: "",
|
cabinetId: "",
|
||||||
lockerId: "",
|
lockerId: "",
|
||||||
@@ -117,20 +113,17 @@ function KeySwap() {
|
|||||||
);
|
);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: error.message,
|
message: error.message,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
@@ -169,51 +162,22 @@ function KeySwap() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{notification.visible && (
|
{notification.message !== "" && <Notification {...notification} />}
|
||||||
<motion.div
|
</AnimatePresence>
|
||||||
initial={{ opacity: 0 }}
|
<div className="relative">
|
||||||
animate={{ opacity: 1 }}
|
{notification.type === "success" && (
|
||||||
exit={{ opacity: 0 }}
|
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
|
||||||
className={clsx(
|
|
||||||
"p-4 mb-8 font-body text-center text-lg rounded-2xl flex items-center justify-center gap-2",
|
|
||||||
notification.type === "error"
|
|
||||||
? "bg-error-surface text-error"
|
|
||||||
: "bg-success-surface text-success"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{notification.message.split(":").map((msg, index) => {
|
|
||||||
return index === 1 ? (
|
|
||||||
<span key={index} className="border-b border-dashed">
|
|
||||||
{msg}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span key={index}>{msg}</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<Copy
|
|
||||||
cursor={"pointer"}
|
|
||||||
size={15}
|
|
||||||
onClick={navigator.clipboard.writeText(
|
|
||||||
notification.message.split(":")[1].trim()
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
<FormBox title={t("lockerStatus")}>
|
||||||
<div className="relative">
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
{notification.type === "success" && (
|
{formFields.map(renderField)}
|
||||||
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
|
</div>
|
||||||
)}
|
<Button text={t("submit")} onClick={handleKeySwap} />
|
||||||
<FormBox title={t("lockerStatus")}>
|
</FormBox>
|
||||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
|
||||||
{formFields.map(renderField)}
|
|
||||||
</div>
|
</div>
|
||||||
<Button text={t("submit")} onClick={handleKeySwap} />
|
|
||||||
</FormBox>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KeySwap;
|
export default KeySwap;
|
||||||
|
|||||||
@@ -4,16 +4,15 @@ import FormInput from "../components/FormInput";
|
|||||||
import FormSelect from "../components/FormSelect";
|
import FormSelect from "../components/FormSelect";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useToast } from "../hooks/useToast";
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import { lockerService } from "../services/locker.service";
|
import { lockerService } from "../services/locker.service";
|
||||||
import { useLoading } from "../hooks/useLoading";
|
import { useLoading } from "../hooks/useLoading";
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence } from "motion/react";
|
||||||
|
import Notification from "../components/Notification";
|
||||||
|
|
||||||
function LockerStatus() {
|
function LockerStatus() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const showToast = useToast();
|
|
||||||
const [lockerDetails, setLockerDetails] = useState({
|
const [lockerDetails, setLockerDetails] = useState({
|
||||||
cabinetId: "",
|
cabinetId: "",
|
||||||
lockerId: "",
|
lockerId: "",
|
||||||
@@ -23,11 +22,7 @@ function LockerStatus() {
|
|||||||
statusValid: true,
|
statusValid: true,
|
||||||
});
|
});
|
||||||
const { isLoading, setIsLoading } = useLoading();
|
const { isLoading, setIsLoading } = useLoading();
|
||||||
const [notification, setNotification] = useState({
|
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||||
visible: false,
|
|
||||||
message: "",
|
|
||||||
type: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const formFields = [
|
const formFields = [
|
||||||
{
|
{
|
||||||
@@ -76,7 +71,6 @@ function LockerStatus() {
|
|||||||
setLockerDetails(newValidationState);
|
setLockerDetails(newValidationState);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
showToast("Highlighted fields are invalid", "error");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +82,14 @@ function LockerStatus() {
|
|||||||
lockerDetails.status
|
lockerDetails.status
|
||||||
);
|
);
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(error.response.data.message, "error");
|
setNotification({
|
||||||
|
message: error.message,
|
||||||
|
type: "error",
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
@@ -102,6 +98,7 @@ function LockerStatus() {
|
|||||||
const renderField = (field) => {
|
const renderField = (field) => {
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
value: lockerDetails[field.name],
|
value: lockerDetails[field.name],
|
||||||
|
valid: lockerDetails[`${field.name}Valid`],
|
||||||
onChange: (e) => {
|
onChange: (e) => {
|
||||||
const newLockerDetails = { ...lockerDetails };
|
const newLockerDetails = { ...lockerDetails };
|
||||||
newLockerDetails[field.name] = e.target.value.toUpperCase();
|
newLockerDetails[field.name] = e.target.value.toUpperCase();
|
||||||
@@ -130,32 +127,18 @@ function LockerStatus() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{notification.visible && (
|
{notification.message !== "" && <Notification {...notification} />}
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className={clsx(
|
|
||||||
"p-4 mb-8 font-body text-center text-xl rounded-2xl flex items-center justify-center gap-2",
|
|
||||||
notification.type === "error"
|
|
||||||
? "bg-error-surface text-error"
|
|
||||||
: "bg-success-surface text-success"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{notification.message}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{notification.type === "success" && (
|
{notification.type === "success" && (
|
||||||
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
|
<div className="absolute inset-0 bg-[#fff]/50 z-10 rounded-3xl" />
|
||||||
)}
|
)}
|
||||||
<FormBox title={t("lockerStatus")}>
|
<FormBox title={t("lockerStatus")}>
|
||||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||||
{formFields.map(renderField)}
|
{formFields.map(renderField)}
|
||||||
</div>
|
</div>
|
||||||
<Button text={t("submit")} onClick={handleSubmit} />
|
<Button text={t("submit")} onClick={handleSubmit} />
|
||||||
</FormBox>
|
</FormBox>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,24 +5,23 @@ import FormBox from "../components/FormBox";
|
|||||||
import Button from "../components/Button";
|
import Button from "../components/Button";
|
||||||
import { useToast } from "../hooks/useToast";
|
import { useToast } from "../hooks/useToast";
|
||||||
import { lockerService } from "../services/locker.service";
|
import { lockerService } from "../services/locker.service";
|
||||||
import { Copy } from "lucide-react";
|
|
||||||
import { AnimatePresence } from "motion/react";
|
import { AnimatePresence } from "motion/react";
|
||||||
import { motion } from "motion/react";
|
|
||||||
import { useLoading } from "../hooks/useLoading";
|
import { useLoading } from "../hooks/useLoading";
|
||||||
|
import Notification from "../components/Notification";
|
||||||
|
|
||||||
function LockersRegistration() {
|
function LockersRegistration() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const showToast = useToast();
|
const showToast = useToast();
|
||||||
const { setIsLoading } = useLoading();
|
const { setIsLoading } = useLoading();
|
||||||
const { noOfLockers, cabinetId } = location.state;
|
const noOfLockers = location.state?.noOfLockers;
|
||||||
|
const cabinetId = location.state?.cabinetId;
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
const [notification, setNotification] = useState({
|
const [notification, setNotification] = useState({
|
||||||
visible: false,
|
|
||||||
message: "",
|
message: "",
|
||||||
type: "",
|
type: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const initLockers = Array(parseInt(noOfLockers))
|
const initLockers = Array(noOfLockers ? parseInt(noOfLockers) : 0)
|
||||||
.fill()
|
.fill()
|
||||||
.map(() => ({
|
.map(() => ({
|
||||||
id: "",
|
id: "",
|
||||||
@@ -109,14 +108,12 @@ function LockersRegistration() {
|
|||||||
lockerValues
|
lockerValues
|
||||||
);
|
);
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: `Cabinet creation successful. Cabinet ID: ${response.data.cabinetId}`,
|
message: `Cabinet creation successful. Cabinet ID: ${response.data.cabinetId}`,
|
||||||
type: "success",
|
type: "success",
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
setNotification({
|
setNotification({
|
||||||
visible: true,
|
|
||||||
message: `Error registering lockers. ${error.message}`,
|
message: `Error registering lockers. ${error.message}`,
|
||||||
type: "error",
|
type: "error",
|
||||||
});
|
});
|
||||||
@@ -207,36 +204,7 @@ function LockersRegistration() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{notification.visible && (
|
{notification.message !== "" && <Notification {...notification} />}
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
exit={{ opacity: 0 }}
|
|
||||||
className={clsx(
|
|
||||||
"p-4 mb-8 font-body text-center text-xl rounded-2xl flex items-center justify-center gap-2",
|
|
||||||
notification.type === "error"
|
|
||||||
? "bg-error-surface text-error"
|
|
||||||
: "bg-success-surface text-success"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{notification.type === "error" ? notification.message : notification.message.split(":").map((msg, index) => {
|
|
||||||
return index === 1 ? (
|
|
||||||
<span key={index} className="border-b border-dashed">
|
|
||||||
{msg}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span key={index}>{msg}</span>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
<Copy
|
|
||||||
cursor={"pointer"}
|
|
||||||
size={15}
|
|
||||||
onClick={navigator.clipboard.writeText(
|
|
||||||
notification.message.split(":")[1].trim()
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
{notification.type === "success" && (
|
{notification.type === "success" && (
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import api from './api';
|
import api from "./api";
|
||||||
|
|
||||||
export const lockerService = {
|
export const lockerService = {
|
||||||
registerLockers: async (cabinetId, lockers) => {
|
registerLockers: async (cabinetId, lockers) => {
|
||||||
return api.post('/cabinet', {
|
return api.post("/cabinet", {
|
||||||
cabinetId,
|
cabinetId,
|
||||||
lockers: lockers.map(({ id, size, keyId }) => ({
|
lockers: lockers.map(({ id, size, keyId }) => ({
|
||||||
id,
|
id,
|
||||||
size,
|
size,
|
||||||
keyId
|
keyId,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -17,14 +17,31 @@ export const lockerService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
keySwap: async (cabinetId, lockerId, reason, oldKey, newKey) => {
|
keySwap: async (cabinetId, lockerId, reason, oldKey, newKey) => {
|
||||||
return api.patch(`/locker/key`, { cabinetId, lockerId, reason, oldKey, newKey });
|
return api.patch(`/locker/key`, {
|
||||||
|
cabinetId,
|
||||||
|
lockerId,
|
||||||
|
reason,
|
||||||
|
oldKey,
|
||||||
|
newKey,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCharges: async (productCode, interestCategory, rent, penalty) => {
|
updateCharges: async (productCode, interestCategory, rent, penalty) => {
|
||||||
return api.patch(`/charge/${productCode}${interestCategory}`, { rent, penalty });
|
return api.patch(`/charge/${productCode}${interestCategory}`, {
|
||||||
|
rent,
|
||||||
|
penalty,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getCharges: async (productCode, interestCategory) => {
|
getCharges: async (productCode, interestCategory) => {
|
||||||
return api.get(`/charge/${productCode}${interestCategory}`);
|
return api.get(`/charge/${productCode}${interestCategory}`);
|
||||||
}
|
},
|
||||||
};
|
|
||||||
|
preCheckIn: async (accountNumber) => {
|
||||||
|
return api.post(`/pre-checkin/${accountNumber}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkInOut: async (accountNumber, time, checkType) => {
|
||||||
|
return api.post(`/check-in-out/${accountNumber}`, { time, checkType });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -11,18 +11,18 @@ export default {
|
|||||||
black: '#000000',
|
black: '#000000',
|
||||||
grey: '#979797',
|
grey: '#979797',
|
||||||
error: {
|
error: {
|
||||||
DEFAULT: '#E5254B',
|
DEFAULT: '#A14444',
|
||||||
dark: '#E5254B',
|
dark: '#E5254B',
|
||||||
surface: {DEFAULT: '#FCE9ED', dark: '#FCE9ED'}
|
surface: {DEFAULT: '#D26464', dark: '#FCE9ED'}
|
||||||
},
|
},
|
||||||
onToast: {
|
onToast: {
|
||||||
DEFAULT: '#646564',
|
DEFAULT: '#646564',
|
||||||
dark: '#646564',
|
dark: '#646564',
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
DEFAULT: '#EA7000',
|
DEFAULT: '#A5A513',
|
||||||
dark: '#EA7000',
|
dark: '#EA7000',
|
||||||
surface: {DEFAULT: '#FDF1E5', dark: '#FDF1E5'}
|
surface: {DEFAULT: '#C8C820', dark: '#FDF1E5'}
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
DEFAULT: '#038100',
|
DEFAULT: '#038100',
|
||||||
|
|||||||
Reference in New Issue
Block a user