Compare commits

..

15 Commits

Author SHA1 Message Date
962102d44c refactor: enhance FormInput and FormSelect components with validation feedback and layout adjustments 2024-12-24 01:31:07 +05:30
03c4988ff1 refactor: streamline layout and error handling in CabinetCreation component 2024-12-24 01:06:10 +05:30
27f4597348 refactor: simplify notification state management across components 2024-12-24 01:03:46 +05:30
265d0b2209 refactor: update FormBox title in ChargeManagement component 2024-12-24 00:51:13 +05:30
a56f643301 feat: add CheckInOutLog page and integrate with locker service for check-in/out functionality 2024-12-24 00:50:06 +05:30
8b34a69dca refactor: update Notification component to use message and type props 2024-12-24 00:40:12 +05:30
f7fea99f30 feat: integrate navigation to log page upon successful account validation in CheckInOutManagement 2024-12-24 00:18:32 +05:30
bd461995c7 refactor: improve ChargeEdit component structure 2024-12-24 00:15:51 +05:30
17681e64ad renamed CheckInOut to CheckInOutManagement 2024-12-24 00:15:08 +05:30
154eba0474 fix: update button disabled state color for better accessibility 2024-12-24 00:05:09 +05:30
dea0047007 refactor: replace inline notification handling with Notification component in LockerStatus 2024-12-24 00:01:29 +05:30
f4b7027708 refactor: replace inline notification rendering with Notification component in LockersRegistration 2024-12-23 23:57:52 +05:30
b4b193a9fe feat: add Check In/Out management with notification system and update locker service 2024-12-23 23:49:35 +05:30
9d33cb5372 fix: handle optional state properties and improve animation transitions in LockersRegistration component 2024-12-23 20:47:19 +05:30
48b9b70c4a refactor animations for improved transitions in App and AccountCreation components 2024-12-23 20:47:13 +05:30
18 changed files with 482 additions and 305 deletions

View File

@@ -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>

View File

@@ -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}
>

View File

@@ -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;

View File

@@ -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,

View 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;

View File

@@ -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 />
}
],
},

View File

@@ -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"

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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;

View 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;

View File

@@ -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;

View File

@@ -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" && (

View File

@@ -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" && (

View File

@@ -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 });
},
};

View File

@@ -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',