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();
|
||||
return (
|
||||
<LoadingProvider>
|
||||
<div className="flex flex-col min-h-screen">
|
||||
<div className="flex flex-col min-h-screen scrollbar">
|
||||
<Header />
|
||||
<LoadingBarWrapper />
|
||||
<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
|
||||
className="w-full ovwerflow-hidden"
|
||||
key={location.pathname}
|
||||
initial={{ opacity: 0, x: 50 }}
|
||||
animate={{ opacity: 1, x: 0 }}
|
||||
exit={{ opacity: 0, x: 50 }}
|
||||
initial={{ y: 15, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 15, opacity: 0 }}
|
||||
>
|
||||
<AnimatedOutlet />
|
||||
</motion.div>
|
||||
|
||||
@@ -7,7 +7,7 @@ function Button({text, onClick, disabled}) {
|
||||
<motion.button
|
||||
whileHover={!disabled && { scale: 1.05 }}
|
||||
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}
|
||||
disabled={disabled}
|
||||
>
|
||||
|
||||
@@ -1,22 +1,38 @@
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { motion, AnimatePresence } from "motion/react";
|
||||
function FormInput({
|
||||
value,
|
||||
onChange,
|
||||
maxLength=17,
|
||||
valid = true,
|
||||
maxLength = 17,
|
||||
readOnly = false,
|
||||
className = "",
|
||||
type = "text",
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
readOnly={readOnly}
|
||||
value={value}
|
||||
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}`}
|
||||
className={`w-72 h-10 px-2 rounded-full dark:bg-white dark:text-grey border-2 text-grey focus:outline-grey ${className}`}
|
||||
onChange={onChange}
|
||||
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,
|
||||
type: PropTypes.string,
|
||||
maxLength: PropTypes.number,
|
||||
valid: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default FormInput;
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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 (
|
||||
<div>
|
||||
<select
|
||||
value={value}
|
||||
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
|
||||
}
|
||||
onChange={onChange}
|
||||
@@ -19,6 +21,20 @@ function FormSelect({ value, onChange, options, className }) {
|
||||
</option>
|
||||
))}
|
||||
</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,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
className: PropTypes.string,
|
||||
valid: PropTypes.bool,
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
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 ChargeManagement from "./pages/ChargeManagement.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([
|
||||
{
|
||||
@@ -64,7 +64,11 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
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 FormSelect from "../components/FormSelect";
|
||||
import Button from "../components/Button";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import productInfo from "../util/productList";
|
||||
import ProductListTable from "../components/ProductListTable";
|
||||
|
||||
function AccountCreation() {
|
||||
const { t } = useTranslation();
|
||||
const showToast = useToast();
|
||||
const [notification] = useState({
|
||||
visible: false,
|
||||
message: "",
|
||||
@@ -179,7 +177,6 @@ function AccountCreation() {
|
||||
setAccountDetails(newValidationState);
|
||||
|
||||
if (!isValid) {
|
||||
showToast("Highlighted fields are invalid", "error");
|
||||
return;
|
||||
}
|
||||
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"
|
||||
>
|
||||
<motion.div
|
||||
initial={{ scale: 0.8 }}
|
||||
animate={{ scale: 1 }}
|
||||
exit={{ scale: 0.8 }}
|
||||
initial={{ y: 15, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
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"
|
||||
>
|
||||
<h2 className="text-xl mb-4">Select Product</h2>
|
||||
@@ -216,6 +213,7 @@ function AccountCreation() {
|
||||
const renerField = (field) => {
|
||||
const commonProps = {
|
||||
value: accountDetails[field.name],
|
||||
valid: accountDetails[`${field.name}Valid`],
|
||||
onChange: (e) => {
|
||||
const newAccountDetails = { ...accountDetails };
|
||||
newAccountDetails[field.name] = e.target.value;
|
||||
@@ -246,9 +244,9 @@ function AccountCreation() {
|
||||
<AnimatePresence>
|
||||
{notification.visible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
initial={{ y: 15, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 15, 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"
|
||||
|
||||
@@ -36,7 +36,6 @@ function CabinetCreation() {
|
||||
});
|
||||
};
|
||||
return (
|
||||
<motion.div className="w-full h-fit" initial={{ scale: 0.9 }} animate={{ scale: 1 }} exit={{ scale: 0 }}>
|
||||
<FormBox title={t("cabinetCreation")}>
|
||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||
<div className="flex">
|
||||
@@ -63,9 +62,9 @@ function CabinetCreation() {
|
||||
{!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 }}
|
||||
initial={{ opacity: 0, scale: 0 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0 }}
|
||||
key="cabinetIdError"
|
||||
>
|
||||
Invalid Cabinet Id
|
||||
@@ -95,9 +94,7 @@ function CabinetCreation() {
|
||||
maxLength={6}
|
||||
/>
|
||||
{!cabinetKeyId.valid && (
|
||||
<div className="text-sm text-error ml-3 pt-1">
|
||||
Invalid Key Id
|
||||
</div>
|
||||
<div className="text-sm text-error ml-3 pt-1">Invalid Key Id</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -132,7 +129,6 @@ function CabinetCreation() {
|
||||
}}
|
||||
/>
|
||||
</FormBox>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -45,13 +45,13 @@ function CabinetMaintenace() {
|
||||
</option>
|
||||
<option value="create">{t("create")}</option>
|
||||
</select>
|
||||
<AnimatePresence initial={false}>
|
||||
<AnimatePresence>
|
||||
{!operation.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 }}
|
||||
initial={{ y: 15, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
exit={{ y: 15, opacity: 0 }}
|
||||
key="cabinetIdError"
|
||||
>
|
||||
Invalid Operation
|
||||
@@ -60,7 +60,6 @@ function CabinetMaintenace() {
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Button text={t("next")} onClick={(e) => handleNext(e)} />
|
||||
</FormBox>
|
||||
|
||||
@@ -6,12 +6,11 @@ import FormSelect from "../components/FormSelect";
|
||||
import Button from "../components/Button";
|
||||
import FormBox from "../components/FormBox";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { lockerService } from "../services/locker.service";
|
||||
import clsx from "clsx";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
import { Pencil } from "lucide-react";
|
||||
import Notification from "../components/Notification";
|
||||
|
||||
function ChargeEdit() {
|
||||
const [chargeDetails, setChargeDetails] = useState({
|
||||
@@ -21,23 +20,22 @@ function ChargeEdit() {
|
||||
penaltyAmountEdit: false,
|
||||
});
|
||||
|
||||
const [notification, setNotification] = useState({
|
||||
visible: false,
|
||||
message: "",
|
||||
type: "",
|
||||
});
|
||||
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||
|
||||
const { setIsLoading } = useLoading();
|
||||
const { t } = useTranslation();
|
||||
const showToast = useToast();
|
||||
const location = useLocation();
|
||||
const { productCode, interestCategory } = location.state;
|
||||
const productCode = location.state?.productCode;
|
||||
const interestCategory = location.state?.interestCategory;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCharges = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await lockerService.getCharges(productCode, interestCategory);
|
||||
const response = await lockerService.getCharges(
|
||||
productCode,
|
||||
interestCategory
|
||||
);
|
||||
if (response.status === 200) {
|
||||
const { rent, penalty } = response.data;
|
||||
setChargeDetails({
|
||||
@@ -48,7 +46,6 @@ function ChargeEdit() {
|
||||
});
|
||||
} else {
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: response.data.message,
|
||||
type: "error",
|
||||
});
|
||||
@@ -56,7 +53,6 @@ function ChargeEdit() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: error.message,
|
||||
type: "error",
|
||||
});
|
||||
@@ -67,6 +63,10 @@ function ChargeEdit() {
|
||||
fetchCharges();
|
||||
}, [productCode, interestCategory, setIsLoading]);
|
||||
|
||||
if (!location.state) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const formFields = [
|
||||
{
|
||||
name: "rentAmount",
|
||||
@@ -75,10 +75,12 @@ function ChargeEdit() {
|
||||
subType: "number",
|
||||
readOnly: !chargeDetails.rentAmountEdit,
|
||||
icon: {
|
||||
icon: <Pencil size={22}/>,
|
||||
icon: <Pencil size={22} />,
|
||||
mode: "plain",
|
||||
onClick: () => {setChargeDetails({...chargeDetails, rentAmountEdit: true})},
|
||||
}
|
||||
onClick: () => {
|
||||
setChargeDetails({ ...chargeDetails, rentAmountEdit: true });
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "penaltyAmount",
|
||||
@@ -87,20 +89,18 @@ function ChargeEdit() {
|
||||
subType: "number",
|
||||
readOnly: !chargeDetails.penaltyAmountEdit,
|
||||
icon: {
|
||||
icon: <Pencil size={22}/>,
|
||||
icon: <Pencil size={22} />,
|
||||
mode: "plain",
|
||||
onClick: () => {setChargeDetails({...chargeDetails, penaltyAmountEdit: true})},
|
||||
}
|
||||
onClick: () => {
|
||||
setChargeDetails({ ...chargeDetails, penaltyAmountEdit: true });
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if(!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit) {
|
||||
showToast("No changes made", "warning");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await lockerService.updateCharges(
|
||||
@@ -111,13 +111,11 @@ function ChargeEdit() {
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: response.data.message,
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: response.data.message,
|
||||
type: "error",
|
||||
});
|
||||
@@ -125,7 +123,6 @@ function ChargeEdit() {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: error.message,
|
||||
type: "error",
|
||||
});
|
||||
@@ -164,21 +161,7 @@ function ChargeEdit() {
|
||||
return (
|
||||
<div>
|
||||
<AnimatePresence>
|
||||
{notification.visible && (
|
||||
<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>
|
||||
)}
|
||||
{notification.message !== "" && <Notification {...notification} />}
|
||||
</AnimatePresence>
|
||||
<div className="relative">
|
||||
{notification.type === "success" && (
|
||||
@@ -188,7 +171,13 @@ function ChargeEdit() {
|
||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||
{formFields.map(renderField)}
|
||||
</div>
|
||||
<Button text={t("submit")} onClick={handleSubmit} disabled={!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit}/>
|
||||
<Button
|
||||
text={t("submit")}
|
||||
onClick={handleSubmit}
|
||||
disabled={
|
||||
!chargeDetails.rentAmountEdit && !chargeDetails.penaltyAmountEdit
|
||||
}
|
||||
/>
|
||||
</FormBox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -102,7 +102,7 @@ function ChargeManagement() {
|
||||
};
|
||||
|
||||
return (
|
||||
<FormBox title={t("lockerStatus")}>
|
||||
<FormBox title={t("chargeManagement")}>
|
||||
<div className="p-2 pt-7 flex flex-col gap-4">
|
||||
{formFields.map(renderField)}
|
||||
</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 { motion, AnimatePresence } from "motion/react";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import { useLoading } from "../hooks/useLoading";
|
||||
@@ -10,17 +10,13 @@ import Button from "../components/Button";
|
||||
import { lockerService } from "../services/locker.service";
|
||||
import clsx from "clsx";
|
||||
import FormBox from "../components/FormBox";
|
||||
import { Copy } from "lucide-react";
|
||||
import Notification from "../components/Notification";
|
||||
|
||||
function KeySwap() {
|
||||
const { t } = useTranslation();
|
||||
const showToast = useToast();
|
||||
const { isLoading, setIsLoading } = useLoading();
|
||||
const [notification, setNotification] = useState({
|
||||
visible: false,
|
||||
message: "",
|
||||
type: "",
|
||||
});
|
||||
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||
const [keySwapDetails, setKeySwapDetails] = useState({
|
||||
cabinetId: "",
|
||||
lockerId: "",
|
||||
@@ -117,20 +113,17 @@ function KeySwap() {
|
||||
);
|
||||
if (response.status === 200) {
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: response.data.message,
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: response.data.message,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: error.message,
|
||||
type: "error",
|
||||
});
|
||||
@@ -170,36 +163,7 @@ function KeySwap() {
|
||||
return (
|
||||
<div>
|
||||
<AnimatePresence>
|
||||
{notification.visible && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
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>
|
||||
)}
|
||||
{notification.message !== "" && <Notification {...notification} />}
|
||||
</AnimatePresence>
|
||||
<div className="relative">
|
||||
{notification.type === "success" && (
|
||||
@@ -213,7 +177,7 @@ function KeySwap() {
|
||||
</FormBox>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export default KeySwap;
|
||||
|
||||
@@ -4,16 +4,15 @@ import FormInput from "../components/FormInput";
|
||||
import FormSelect from "../components/FormSelect";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import clsx from "clsx";
|
||||
import Button from "../components/Button";
|
||||
import { lockerService } from "../services/locker.service";
|
||||
import { useLoading } from "../hooks/useLoading";
|
||||
import { AnimatePresence, motion } from "motion/react";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
import Notification from "../components/Notification";
|
||||
|
||||
function LockerStatus() {
|
||||
const { t } = useTranslation();
|
||||
const showToast = useToast();
|
||||
const [lockerDetails, setLockerDetails] = useState({
|
||||
cabinetId: "",
|
||||
lockerId: "",
|
||||
@@ -23,11 +22,7 @@ function LockerStatus() {
|
||||
statusValid: true,
|
||||
});
|
||||
const { isLoading, setIsLoading } = useLoading();
|
||||
const [notification, setNotification] = useState({
|
||||
visible: false,
|
||||
message: "",
|
||||
type: "",
|
||||
});
|
||||
const [notification, setNotification] = useState({ message: "", type: "" });
|
||||
|
||||
const formFields = [
|
||||
{
|
||||
@@ -76,7 +71,6 @@ function LockerStatus() {
|
||||
setLockerDetails(newValidationState);
|
||||
|
||||
if (!isValid) {
|
||||
showToast("Highlighted fields are invalid", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,12 +82,14 @@ function LockerStatus() {
|
||||
lockerDetails.status
|
||||
);
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: response.data.message,
|
||||
type: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
showToast(error.response.data.message, "error");
|
||||
setNotification({
|
||||
message: error.message,
|
||||
type: "error",
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -102,6 +98,7 @@ function LockerStatus() {
|
||||
const renderField = (field) => {
|
||||
const commonProps = {
|
||||
value: lockerDetails[field.name],
|
||||
valid: lockerDetails[`${field.name}Valid`],
|
||||
onChange: (e) => {
|
||||
const newLockerDetails = { ...lockerDetails };
|
||||
newLockerDetails[field.name] = e.target.value.toUpperCase();
|
||||
@@ -130,21 +127,7 @@ function LockerStatus() {
|
||||
return (
|
||||
<div>
|
||||
<AnimatePresence>
|
||||
{notification.visible && (
|
||||
<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>
|
||||
)}
|
||||
{notification.message !== "" && <Notification {...notification} />}
|
||||
</AnimatePresence>
|
||||
<div className="relative">
|
||||
{notification.type === "success" && (
|
||||
|
||||
@@ -5,24 +5,23 @@ import FormBox from "../components/FormBox";
|
||||
import Button from "../components/Button";
|
||||
import { useToast } from "../hooks/useToast";
|
||||
import { lockerService } from "../services/locker.service";
|
||||
import { Copy } from "lucide-react";
|
||||
import { AnimatePresence } from "motion/react";
|
||||
import { motion } from "motion/react";
|
||||
import { useLoading } from "../hooks/useLoading";
|
||||
import Notification from "../components/Notification";
|
||||
|
||||
function LockersRegistration() {
|
||||
const location = useLocation();
|
||||
const showToast = useToast();
|
||||
const { setIsLoading } = useLoading();
|
||||
const { noOfLockers, cabinetId } = location.state;
|
||||
const noOfLockers = location.state?.noOfLockers;
|
||||
const cabinetId = location.state?.cabinetId;
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [notification, setNotification] = useState({
|
||||
visible: false,
|
||||
message: "",
|
||||
type: "",
|
||||
});
|
||||
|
||||
const initLockers = Array(parseInt(noOfLockers))
|
||||
const initLockers = Array(noOfLockers ? parseInt(noOfLockers) : 0)
|
||||
.fill()
|
||||
.map(() => ({
|
||||
id: "",
|
||||
@@ -109,14 +108,12 @@ function LockersRegistration() {
|
||||
lockerValues
|
||||
);
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: `Cabinet creation successful. Cabinet ID: ${response.data.cabinetId}`,
|
||||
type: "success",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setNotification({
|
||||
visible: true,
|
||||
message: `Error registering lockers. ${error.message}`,
|
||||
type: "error",
|
||||
});
|
||||
@@ -207,36 +204,7 @@ function LockersRegistration() {
|
||||
return (
|
||||
<div>
|
||||
<AnimatePresence>
|
||||
{notification.visible && (
|
||||
<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>
|
||||
)}
|
||||
{notification.message !== "" && <Notification {...notification} />}
|
||||
</AnimatePresence>
|
||||
<div className="relative">
|
||||
{notification.type === "success" && (
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import api from './api';
|
||||
import api from "./api";
|
||||
|
||||
export const lockerService = {
|
||||
registerLockers: async (cabinetId, lockers) => {
|
||||
return api.post('/cabinet', {
|
||||
return api.post("/cabinet", {
|
||||
cabinetId,
|
||||
lockers: lockers.map(({ id, size, keyId }) => ({
|
||||
id,
|
||||
size,
|
||||
keyId
|
||||
}))
|
||||
keyId,
|
||||
})),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -17,14 +17,31 @@ export const lockerService = {
|
||||
},
|
||||
|
||||
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) => {
|
||||
return api.patch(`/charge/${productCode}${interestCategory}`, { rent, penalty });
|
||||
return api.patch(`/charge/${productCode}${interestCategory}`, {
|
||||
rent,
|
||||
penalty,
|
||||
});
|
||||
},
|
||||
|
||||
getCharges: async (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',
|
||||
grey: '#979797',
|
||||
error: {
|
||||
DEFAULT: '#E5254B',
|
||||
DEFAULT: '#A14444',
|
||||
dark: '#E5254B',
|
||||
surface: {DEFAULT: '#FCE9ED', dark: '#FCE9ED'}
|
||||
surface: {DEFAULT: '#D26464', dark: '#FCE9ED'}
|
||||
},
|
||||
onToast: {
|
||||
DEFAULT: '#646564',
|
||||
dark: '#646564',
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: '#EA7000',
|
||||
DEFAULT: '#A5A513',
|
||||
dark: '#EA7000',
|
||||
surface: {DEFAULT: '#FDF1E5', dark: '#FDF1E5'}
|
||||
surface: {DEFAULT: '#C8C820', dark: '#FDF1E5'}
|
||||
},
|
||||
success: {
|
||||
DEFAULT: '#038100',
|
||||
|
||||
Reference in New Issue
Block a user