diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
index f9cb827..8c9d43e 100644
--- a/public/locales/en/translation.json
+++ b/public/locales/en/translation.json
@@ -66,5 +66,17 @@
"customerType": "Customer Type",
"collateralFDAccount": "Collateral FD Account",
"rentPayAccount": "Rent Pay Account",
- "submit": "Submit"
+ "submit": "Submit",
+ "lockerId": "Locker ID",
+ "status": "Status",
+ "lockerStatus": "Locker Status",
+ "open": "Open",
+ "close": "Close",
+ "reasonForChange": "Reason for Change",
+ "oldKeyId": "Old Key ID",
+ "newKeyId": "New Key ID",
+ "confirmNewKeyId": "Confirm New Key ID",
+ "highlightedFieldsInvalid": "Highlighted fields are invalid",
+ "changeStatus": "Change Status",
+ "keySwap": "Key Swap"
}
\ No newline at end of file
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
index 8d954cd..c2cf242 100644
--- a/src/components/Header.jsx
+++ b/src/components/Header.jsx
@@ -26,11 +26,10 @@ function Header() {
submenu: [
{ name: "accountCreation", path: "operation/account" },
{ name: "cabinetMaintenance", path: "operation/cabinet" },
- { name: "lockerMaintenance", path: "locker-maintenance" },
- { name: "rentPenaltyCollection", path: "rent-collection" },
- { name: "chargeManagement", path: "charge-management" },
- { name: "checkInOutManagement", path: "check-in-out" },
- { name: "accountSurrender", path: "account-surrender" }
+ { name: "lockerMaintenance", path: "operation/locker" },
+ { name: "chargeManagement", path: "operation/charge-management" },
+ { name: "checkInOutManagement", path: "operation/check-in-out" },
+ { name: "accountSurrender", path: "operation/account-surrender" }
],
},
{ name: "worklist", submenu: [{ name: "myIntimation", path: "my-intimation" }] },
diff --git a/src/main.jsx b/src/main.jsx
index 3590604..b5138f8 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -9,6 +9,9 @@ import CabinetMaintenace from "./pages/CabinetMaintenance.jsx";
import CabinetCreation from "./pages/CabinetCreation.jsx";
import LockersRegistration from "./pages/LockersRegistration.jsx";
import AccountCreation from "./pages/AccountCreation.jsx";
+import LockerMaintenance from "./pages/LockerMaintenance.jsx";
+import LockerStatus from "./pages/LockerStatus.jsx";
+import KeySwap from "./pages/KeySwap.jsx";
const router = createBrowserRouter([
{
@@ -34,6 +37,18 @@ const router = createBrowserRouter([
{
path: "operation/account",
element: ,
+ },
+ {
+ path: "operation/locker",
+ element: ,
+ },
+ {
+ path: "operation/locker/status",
+ element: ,
+ },
+ {
+ path: "operation/locker/key-swap",
+ element: ,
}
],
},
diff --git a/src/pages/KeySwap.jsx b/src/pages/KeySwap.jsx
new file mode 100644
index 0000000..076c641
--- /dev/null
+++ b/src/pages/KeySwap.jsx
@@ -0,0 +1,219 @@
+import { useState } from "react";
+import { motion, AnimatePresence } from "motion/react";
+import { useTranslation } from "react-i18next";
+import { useToast } from "../hooks/useToast";
+import { useLoading } from "../hooks/useLoading";
+import FormField from "../components/FormField";
+import FormInput from "../components/FormInput";
+import FormSelect from "../components/FormSelect";
+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";
+
+function KeySwap() {
+ const { t } = useTranslation();
+ const showToast = useToast();
+ const { isLoading, setIsLoading } = useLoading();
+ const [notification, setNotification] = useState({
+ visible: false,
+ message: "",
+ type: "",
+ });
+ const [keySwapDetails, setKeySwapDetails] = useState({
+ cabinetId: "",
+ lockerId: "",
+ reason: "",
+ oldKey: "",
+ newKey: "",
+ newKeyConfirm: "",
+ cabinetIdValid: true,
+ lockerIdValid: true,
+ reasonValid: true,
+ oldKeyValid: true,
+ newKeyValid: true,
+ newKeyConfirmValid: true,
+ });
+
+ const formFields = [
+ {
+ name: "cabinetId",
+ label: t("cabinetId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
+ },
+ {
+ name: "lockerId",
+ label: t("lockerId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
+ },
+ {
+ name: "reason",
+ label: t("reasonForChange"),
+ type: "input",
+ maxLength: 50,
+ readOnly: isLoading,
+ validate: (value) => value !== "",
+ },
+ {
+ name: "oldKey",
+ label: t("oldKeyId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
+ },
+ {
+ name: "newKey",
+ label: t("newKeyId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
+ },
+ {
+ name: "newKeyConfirm",
+ label: t("confirmNewKeyId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => value !== "" && value === keySwapDetails.newKey,
+ },
+ ];
+
+ const handleKeySwap = async (e) => {
+ e.preventDefault();
+ let valid = true;
+
+ for (const field of formFields) {
+ if (!field.validate(keySwapDetails[field.name])) {
+ valid = false;
+ setKeySwapDetails((prev) => ({
+ ...prev,
+ [`${field.name}Valid`]: false,
+ }));
+ }
+ }
+
+ if (!valid) {
+ showToast(t("highlightedFieldsInvalid"), "error");
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const response = await lockerService.keySwap(
+ keySwapDetails.cabinetId,
+ keySwapDetails.lockerId,
+ keySwapDetails.reason,
+ keySwapDetails.oldKey,
+ keySwapDetails.newKey
+ );
+ 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",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const renderField = (field) => {
+ const commonProps = {
+ value: keySwapDetails[field.name],
+ onChange: (e) => {
+ const newLockerDetails = { ...keySwapDetails };
+ newLockerDetails[field.name] = e.target.value.toUpperCase();
+ newLockerDetails[`${field.name}Valid`] = true;
+ setKeySwapDetails(newLockerDetails);
+ },
+ maxLength: field.maxLength,
+ className: clsx(!keySwapDetails[`${field.name}Valid`] && "border-error"),
+ };
+
+ return (
+
+ {field.type === "input" ? (
+
+ ) : (
+
+ )}
+
+ );
+ };
+
+ return (
+
+
+ {notification.visible && (
+
+ {notification.message.split(":").map((msg, index) => {
+ return index === 1 ? (
+
+ {msg}
+
+ ) : (
+ {msg}
+ );
+ })}
+
+
+ )}
+
+
+ {notification.type === "success" && (
+
+ )}
+
+
+ {formFields.map(renderField)}
+
+
+
+
+
+ )
+}
+
+export default KeySwap;
diff --git a/src/pages/LockerMaintenance.jsx b/src/pages/LockerMaintenance.jsx
new file mode 100644
index 0000000..8cf0eb1
--- /dev/null
+++ b/src/pages/LockerMaintenance.jsx
@@ -0,0 +1,70 @@
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { useTranslation } from "react-i18next";
+import FormBox from "../components/FormBox";
+import Button from "../components/Button";
+import { motion, AnimatePresence } from "motion/react";
+import clsx from "clsx";
+
+function LockerMaintenance() {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+ const [operation, setOperation] = useState({ value: "", valid: true });
+
+ const handleNext = (e) => {
+ e.preventDefault();
+ if (operation.value === "") {
+ setOperation({ value: operation.value, valid: false });
+ }
+ navigate(operation.value);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {!operation.valid && (
+
+ Invalid Operation
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+export default LockerMaintenance;
diff --git a/src/pages/LockerStatus.jsx b/src/pages/LockerStatus.jsx
new file mode 100644
index 0000000..3aaddea
--- /dev/null
+++ b/src/pages/LockerStatus.jsx
@@ -0,0 +1,164 @@
+import FormBox from "../components/FormBox";
+import FormField from "../components/FormField";
+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";
+
+function LockerStatus() {
+ const { t } = useTranslation();
+ const showToast = useToast();
+ const [lockerDetails, setLockerDetails] = useState({
+ cabinetId: "",
+ lockerId: "",
+ status: "",
+ cabinetIdValid: true,
+ lockerIdValid: true,
+ statusValid: true,
+ });
+ const { isLoading, setIsLoading } = useLoading();
+ const [notification, setNotification] = useState({
+ visible: false,
+ message: "",
+ type: "",
+ });
+
+ const formFields = [
+ {
+ name: "cabinetId",
+ label: t("cabinetId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
+ },
+ {
+ name: "lockerId",
+ label: t("lockerId"),
+ type: "input",
+ maxLength: 6,
+ readOnly: isLoading,
+ validate: (value) => /^[A-Z]{2}[0-9]{4}/.test(value),
+ },
+ {
+ name: "status",
+ label: t("status"),
+ type: "select",
+ readOnly: isLoading,
+ options: [
+ { value: "open", label: t("open") },
+ { value: "close", label: t("close") },
+ ],
+ validate: (value) => value !== "",
+ },
+ ];
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ let isValid = true;
+ const newValidationState = { ...lockerDetails };
+
+ // Validate account details fields
+ formFields.forEach((field) => {
+ if (field.validate) {
+ const fieldIsValid = field.validate(lockerDetails[field.name]);
+ newValidationState[`${field.name}Valid`] = fieldIsValid;
+ if (!fieldIsValid) isValid = false;
+ }
+ });
+
+ setLockerDetails(newValidationState);
+
+ if (!isValid) {
+ showToast("Highlighted fields are invalid", "error");
+ return;
+ }
+
+ try {
+ setIsLoading(true);
+ const response = await lockerService.changeLockerStatus(
+ lockerDetails.cabinetId,
+ lockerDetails.lockerId,
+ lockerDetails.status
+ );
+ setNotification({
+ visible: true,
+ message: response.data.message,
+ type: "success",
+ });
+ } catch (error) {
+ showToast(error.response.data.message, "error");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const renderField = (field) => {
+ const commonProps = {
+ value: lockerDetails[field.name],
+ onChange: (e) => {
+ const newLockerDetails = { ...lockerDetails };
+ newLockerDetails[field.name] = e.target.value.toUpperCase();
+ newLockerDetails[`${field.name}Valid`] = true;
+ setLockerDetails(newLockerDetails);
+ },
+ maxLength: field.maxLength,
+ className: clsx(!lockerDetails[`${field.name}Valid`] && "border-error"),
+ };
+
+ return (
+
+ {field.type === "input" ? (
+
+ ) : (
+
+ )}
+
+ );
+ };
+
+ return (
+
+
+ {notification.visible && (
+
+ {notification.message}
+
+ )}
+
+
+ {notification.type === "success" && (
+
+ )}
+
+
+ {formFields.map(renderField)}
+
+
+
+
+
+ );
+}
+
+export default LockerStatus;
diff --git a/src/pages/LockersRegistration.jsx b/src/pages/LockersRegistration.jsx
index 2ced813..70a64c3 100644
--- a/src/pages/LockersRegistration.jsx
+++ b/src/pages/LockersRegistration.jsx
@@ -4,7 +4,7 @@ import clsx from "clsx";
import FormBox from "../components/FormBox";
import Button from "../components/Button";
import { useToast } from "../hooks/useToast";
-import { lockersService } from "../services/locker.service";
+import { lockerService } from "../services/locker.service";
import { Copy } from "lucide-react";
import { AnimatePresence } from "motion/react";
import { motion } from "motion/react";
@@ -103,7 +103,7 @@ function LockersRegistration() {
try {
setSubmitting(true);
setIsLoading(true);
- const response = await lockersService.registerLockers(
+ const response = await lockerService.registerLockers(
cabinetId,
lockerValues
);
diff --git a/src/services/locker.service.js b/src/services/locker.service.js
index 8e8c0ac..47a0c39 100644
--- a/src/services/locker.service.js
+++ b/src/services/locker.service.js
@@ -1,6 +1,6 @@
import api from './api';
-export const lockersService = {
+export const lockerService = {
registerLockers: async (cabinetId, lockers) => {
return api.post('/cabinet', {
cabinetId,
@@ -11,4 +11,12 @@ export const lockersService = {
}))
});
},
+
+ changeLockerStatus: async (cabinetId, lockerId, status) => {
+ return api.patch(`/locker/status`, { cabinetId, lockerId, status });
+ },
+
+ keySwap: async (cabinetId, lockerId, reason, oldKey, newKey) => {
+ return api.patch(`/locker/key`, { cabinetId, lockerId, reason, oldKey, newKey });
+ }
};
\ No newline at end of file