From 7a0bd7a228fad37a5e22d464292d5efa1ba3c372 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Fri, 8 Nov 2024 12:30:14 +0530 Subject: [PATCH] retailers edit option there where anything can be editable --- package.json | 1 + src/routes.js | 7 + .../EditRetailDistributor.js | 795 ++++++++++++++++++ .../RetailDistributors/RetailDistributor.js | 34 +- .../addRetailDistributor.js | 83 +- .../TerritoryManager/TerritoryManager.js | 4 +- 6 files changed, 885 insertions(+), 39 deletions(-) create mode 100644 src/views/RetailDistributors/EditRetailDistributor.js diff --git a/package.json b/package.json index 0d6de74..66df8f3 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "sweetalert": "^2.1.2", "sweetalert2": "^11.4.0", "uuid": "^9.0.1", + "validator": "^13.12.0", "xlsx": "^0.18.5" }, "devDependencies": { diff --git a/src/routes.js b/src/routes.js index a5b41aa..cb47271 100644 --- a/src/routes.js +++ b/src/routes.js @@ -170,6 +170,7 @@ import UploadOpeningInventory from "./views/PrincipalDistributors/UploadOpeningI import OpeningInventoryReports from "./views/Reports/OpeningInventoryReports"; import StockReports from "./views/Reports/StockReports "; import Transporter from "./views/Transporter/Transporter"; +import EditRetailDistributor from "./views/RetailDistributors/EditRetailDistributor"; const routes = [ //dashboard @@ -398,6 +399,12 @@ const routes = [ element: AddRetailDistributor, navName: "RetailDistributor", }, + { + path: "/retaildistributor/edit/:id", + name: "Edit Retailer", + element: EditRetailDistributor, + navName: "RetailDistributor", + }, { path: "/add-retail-distributor/multiple", name: "Add Retailer", diff --git a/src/views/RetailDistributors/EditRetailDistributor.js b/src/views/RetailDistributors/EditRetailDistributor.js new file mode 100644 index 0000000..a4c1cfb --- /dev/null +++ b/src/views/RetailDistributors/EditRetailDistributor.js @@ -0,0 +1,795 @@ +import React, { useState, useEffect, useCallback } from "react"; +import { + Box, + CircularProgress, + Card, + Button, + Typography, + Grid, + Paper, + Avatar, + IconButton, + Dialog, + DialogContent, + DialogTitle, + Autocomplete, + TextField, +} from "@mui/material"; +import { useNavigate, useParams } from "react-router-dom"; +import { toast } from "react-hot-toast"; +import axios from "axios"; +import { isAutheticated } from "src/auth"; +import { City, State } from "country-state-city"; +import CancelIcon from "@mui/icons-material/Cancel"; +import DeleteIcon from "@mui/icons-material/Delete"; +import validator from "validator"; + +const EditRetailDistributor = () => { + const navigate = useNavigate(); + const token = isAutheticated(); + const { id } = useParams(); + const [kycid, setKycid] = useState(""); + const [user, setUser] = useState({ + name: "", + email: "", + trade_name: "", + address: "", + state: "", + city: "", + district: "", + pincode: "", + mobile_number: "", + pan_number: "", + aadhar_number: "", + gst_number: "", + }); + + const [loading, setLoading] = useState(false); + const [stateOptions, setStateOptions] = useState([]); + const [cityOptions, setCityOptions] = useState([]); + const [selectedState, setSelectedState] = useState(null); + const [selectedCity, setSelectedCity] = useState(null); + const [errors, setErrors] = useState({}); + + // Fetch all available states on mount + useEffect(() => { + const states = State.getStatesOfCountry("IN").map((state) => ({ + label: state.name, + value: state.isoCode, + })); + setStateOptions(states); + }, []); + + // Fetch city options whenever selected state changes + useEffect(() => { + if (selectedState) { + const cities = City.getCitiesOfState("IN", selectedState.value).map( + (city) => ({ + label: city.name, + value: city.name, + }) + ); + setCityOptions(cities); + } else { + setCityOptions([]); // Reset cities if no state selected + } + }, [selectedState]); + + const handleInputChange = (e) => { + setUser({ ...user, [e.target.name]: e.target.value }); + }; + + const handleStateChange = (event, newValue) => { + setSelectedState(newValue); + setUser((prev) => ({ + ...prev, + state: newValue ? newValue.label : "", + city: "", + })); + setSelectedCity(null); // Clear city when state changes + setCityOptions([]); // Reset city options + }; + + const handleCityChange = (event, newValue) => { + setSelectedCity(newValue); + setUser((prev) => ({ + ...prev, + city: newValue ? newValue.label : "", + })); + }; + // Validation function + const validateFields = () => { + const validationErrors = []; + + if (!user.name) validationErrors.push("Name is required."); + if (!user.email || !validator.isEmail(user.email)) { + validationErrors.push("Invalid or missing email."); + } + if (!user.trade_name) validationErrors.push("Trade name is required."); + if (!user.address) validationErrors.push("Address is required."); + if (!user.mobile_number || !/^\d{10}$/.test(user.mobile_number)) { + validationErrors.push("Invalid mobile number (should be 10 digits)."); + } + if ( + !user.pan_number || + !/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(user.pan_number) + ) { + validationErrors.push("Invalid PAN number."); + } + if (!user.aadhar_number || !/^\d{12}$/.test(user.aadhar_number)) { + validationErrors.push("Invalid Aadhar number (should be 12 digits)."); + } + if ( + !user.gst_number || + !/^(\d{2}[A-Z]{5}\d{4}[A-Z]{1}\d[Z]{1}[A-Z\d]{1})$/.test(user.gst_number) + ) { + validationErrors.push("Invalid GST number."); + } + if (!user.state) validationErrors.push("State is required."); + if (!user.city) validationErrors.push("City is required."); + if (!user.pincode || !/^\d{6}$/.test(user.pincode)) { + validationErrors.push("Invalid Postal Code."); + } + + return validationErrors; + }; + const handleFormSubmit = async (e) => { + e.preventDefault(); + // Validate required fields + const validationErrors = validateFields(); + + if (validationErrors.length > 0) { + // Display each validation error in a toast message + validationErrors.forEach((error) => toast.error(error)); + setErrors(validationErrors); + return; + } + + setLoading(true); + const formData = new FormData(); + Object.keys(user).forEach((key) => formData.append(key, user[key])); + + // Append files only if they are modified + // Object.keys(files).forEach((key) => { + // if (files[key] && !(typeof files[key] === "string")) { + // formData.append(key, files[key]); + // } + // }); + // Only include changed images in the formData + Object.entries(files).forEach(([key, value]) => { + if (value?.file) { + formData.append(key, value.file); + } + }); + + try { + const response = await axios.put( + `/api/retailer/update-admin/${id}`, + formData, + { + headers: { + "Content-Type": "multipart/form-data", + Authorization: `Bearer ${token}`, + }, + } + ); + if (response.data.success) { + toast.success("Retailer updated successfully!"); + navigate("/retail-distributor"); + } + } catch (error) { + toast.error(error.response?.data?.message || "Something went wrong!"); + } finally { + setLoading(false); + } + }; + // Helper function to format names + const formatName = (Name) => { + return Name.toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); + }; + const getUserDetails = useCallback(async () => { + try { + const response = await axios.get(`/api/getRD/${id}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + const userData = response.data; + // console.log(userData); + setKycid(userData.kyc._id); + if (userData?.kyc) { + const formattedState = formatName(userData?.kyc?.state); + const stateOption = + stateOptions.find((s) => s.label === formattedState) || null; + setSelectedState(stateOption); + let formattedCity = formatName(userData.kyc.city); + if (stateOption) { + const cities = City.getCitiesOfState("IN", stateOption.value).map( + (city) => ({ + label: city.name, + value: city.name, + }) + ); + setCityOptions(cities); + const cityOption = + cities.find((c) => c.label === formattedCity) || null; + setSelectedCity(cityOption); + } + + setUser({ + name: userData.name, + email: userData.email, + trade_name: userData.kyc.trade_name, + address: userData.kyc.address, + district: userData.kyc.district, + pincode: userData.kyc.pincode, + mobile_number: userData.kyc.mobile_number, + pan_number: userData.kyc.pan_number, + aadhar_number: userData.kyc.aadhar_number, + gst_number: userData.kyc.gst_number, + state: formattedState ? formattedState : userData.kyc.state, + city: formattedCity ? formattedCity : userData.kyc.city, + }); + setFiles({ + selfieEntranceImg: userData.kyc?.selfie_entrance_img, + panImg: userData.kyc?.pan_img, + aadharImg: userData.kyc?.aadhar_img, + gstImg: userData.kyc?.gst_img, + pesticideLicenseImg: userData.kyc?.pesticide_license_img, + fertilizerLicenseImg: userData.kyc?.fertilizer_license_img, + }); + } + } catch (error) { + console.error("Error fetching user details:", error); + } + }, [id, token, stateOptions]); + + // Fetch retailer details on component mount + useEffect(() => { + getUserDetails(); + }, [getUserDetails]); + + const [files, setFiles] = useState({ + selfieEntranceImg: null, + panImg: null, + aadharImg: null, + gstImg: null, + pesticideLicenseImg: null, + fertilizerLicenseImg: null, + }); + const [openPopup, setOpenPopup] = useState(false); + const [selectedImage, setSelectedImage] = useState(""); + + const handleOpenPopup = (imageUrl) => { + setSelectedImage(imageUrl); + setOpenPopup(true); + }; + + const handleClosePopup = () => { + setOpenPopup(false); + setSelectedImage(""); + }; + + const handleFileChange = (e) => { + const { name, files } = e.target; + if (files.length > 0) { + const file = files[0]; + if (["image/png", "image/jpeg", "image/jpg"].includes(file.type)) { + setFiles((prev) => ({ ...prev, [name]: { file } })); + } else { + alert("Only PNG, JPG, and JPEG files are allowed."); + } + } + }; + // console.log(kycid); + const handleDeleteImage = async (name) => { + if (files[name]?.url) { + // Backend deletion + try { + await axios({ + method: "delete", + url: `/api/deleteImage/${files[name].public_id}`, // Specify the image public_id to delete + data: { + kycid: kycid, + imageType: name, + }, + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + setFiles((prev) => ({ ...prev, [name]: null })); + } catch (error) { + console.error("Error deleting image:", error); + } + } else { + // Local deletion + setFiles((prev) => ({ ...prev, [name]: null })); + } + }; + + const renderImageWithDelete = (name, image) => ( +
+ + handleOpenPopup(image.url || URL.createObjectURL(image.file)) + } + /> + handleDeleteImage(name)} + > + + +
+ ); + const handleCancel = () => { + navigate("/retail-distributor"); + }; + return ( +
+ + + + Edit Retailer + +
+ + Basic Information + + + {/* Name */} + + + + Name* + + + + + + + + + + Email* + + + + + + + {/* Trade Name */} + + + + Trade Name* + + + + + + + + {/* Address */} + + + + Address* + + + + + + + + {/* Mobile Number */} + + + + Mobile Number* + + + + + + + + {/* PAN Number */} + + + + PAN Number* + + + + + + + + {/* Aadhar Number */} + + + + Aadhar Number* + + + + + + + + {/* GST Number */} + + + + GST Number* + + + + + + + + {/* State */} + + + + State* + + + + ( + + )} + /> + + + + + + + {/* City */} + + + + City* + + + + + option.value === value.value + } + renderInput={(params) => ( + + )} + /> + + + + + + + {/* District */} + + + + District* + + + + + + + + {/* Pincode */} + + + + Pincode* + + + + + + + + {/* File Uploads */} + + + Upload Documents + + + + {Object.entries({ + panImg: "PAN Image", + aadharImg: "Aadhar Image", + gstImg: "GST Image", + pesticideLicenseImg: "Pesticide License", + fertilizerLicenseImg: "Fertilizer License", + selfieEntranceImg: "Selfie of Entrance Board", + }).map(([name, label]) => ( + + + + {label} + + + {files[name] ? ( + renderImageWithDelete(name, files[name]) + ) : ( + + )} + + + ))} + + + {/* Image Popup */} + + + + + + + + Large Preview + + + + + {/* Submit Button */} + + + + + + + +
+
+
+ ); +}; + +export default EditRetailDistributor; diff --git a/src/views/RetailDistributors/RetailDistributor.js b/src/views/RetailDistributors/RetailDistributor.js index 8c0f8ba..b8eab85 100644 --- a/src/views/RetailDistributors/RetailDistributor.js +++ b/src/views/RetailDistributors/RetailDistributor.js @@ -166,15 +166,15 @@ const RetailDistributor = () => { style={{ background: "#ecdddd" }} > - ID - Trade Name - Created On - Principal Distributor - Territory Manager - Sales Coordinator - Orders - Mapping - Action + ID + Trade Name + Created On + Principal Distributor + Territory Manager + Sales Coordinator + Orders + Mapping + Action @@ -258,7 +258,7 @@ const RetailDistributor = () => { > @@ -287,13 +287,21 @@ const RetailDistributor = () => { > + + + )) diff --git a/src/views/RetailDistributors/addRetailDistributor.js b/src/views/RetailDistributors/addRetailDistributor.js index 23ea330..1251f2e 100644 --- a/src/views/RetailDistributors/addRetailDistributor.js +++ b/src/views/RetailDistributors/addRetailDistributor.js @@ -13,6 +13,7 @@ import { toast } from "react-hot-toast"; import axios from "axios"; import { isAutheticated } from "src/auth"; import { City, State } from "country-state-city"; +import validator from "validator"; const AddRetailDistributor = () => { const navigate = useNavigate(); @@ -98,27 +99,54 @@ const AddRetailDistributor = () => { } } }; + // Validation function + const validateFields = () => { + const validationErrors = []; + if (!user.name) validationErrors.push("Name is required."); + if (!user.email || !validator.isEmail(user.email)) { + validationErrors.push("Invalid or missing email."); + } + if (!user.trade_name) validationErrors.push("Trade name is required."); + if (!user.address) validationErrors.push("Address is required."); + if (!user.mobile_number || !/^\d{10}$/.test(user.mobile_number)) { + validationErrors.push("Invalid mobile number (should be 10 digits)."); + } + if ( + !user.pan_number || + !/^[A-Z]{5}[0-9]{4}[A-Z]{1}$/.test(user.pan_number) + ) { + validationErrors.push("Invalid PAN number."); + } + if (!user.aadhar_number || !/^\d{12}$/.test(user.aadhar_number)) { + validationErrors.push("Invalid Aadhar number (should be 12 digits)."); + } + if ( + !user.gst_number || + !/^(\d{2}[A-Z]{5}\d{4}[A-Z]{1}\d[Z]{1}[A-Z\d]{1})$/.test(user.gst_number) + ) { + validationErrors.push("Invalid GST number."); + } + if (!user.state) validationErrors.push("State is required."); + if (!user.city) validationErrors.push("City is required."); + if (!user.pincode || !/^\d{6}$/.test(user.pincode)) { + validationErrors.push("Invalid Postal Code."); + } + + return validationErrors; + }; const handleFormSubmit = async (e) => { e.preventDefault(); try { - // Validate input fields - if ( - !user.name || - !user.email || - !user.trade_name || - !user.address || - !user.mobile_number || - !user.pan_number || - !user.aadhar_number || - !user.gst_number || - !selectedState || - !selectedCity - ) { - setErrors({ message: "Fill all required fields!" }); + // Validate required fields + const validationErrors = validateFields(); + + if (validationErrors.length > 0) { + // Display each validation error in a toast message + validationErrors.forEach((error) => toast.error(error)); + setErrors(validationErrors); return; } - setLoading(true); const formData = new FormData(); @@ -146,7 +174,7 @@ const AddRetailDistributor = () => { const response = await axios.post("/api/kyc/create-admin/", formData, { headers: { "Content-Type": "multipart/form-data", - "Authorization": `Bearer ${token}`, + Authorization: `Bearer ${token}`, }, }); @@ -495,10 +523,21 @@ const AddRetailDistributor = () => { { label: "PAN Image*", name: "panImg" }, { label: "Aadhar Image*", name: "aadharImg" }, { label: "GST Image*", name: "gstImg" }, - { label: "Pesticide License Image*", name: "pesticideLicenseImg" }, - { label: "Fertilizer License Image*", name: "fertilizerLicenseImg" }, + { + label: "Pesticide License Image*", + name: "pesticideLicenseImg", + }, + { + label: "Fertilizer License Image*", + name: "fertilizerLicenseImg", + }, ].map(({ label, name }) => ( - + { color="primary" disabled={loading} > - {loading ? ( - - ) : ( - "Submit" - )} + {loading ? : "Submit"} diff --git a/src/views/TerritoryManager/TerritoryManager.js b/src/views/TerritoryManager/TerritoryManager.js index 6416fed..7d05514 100644 --- a/src/views/TerritoryManager/TerritoryManager.js +++ b/src/views/TerritoryManager/TerritoryManager.js @@ -303,7 +303,7 @@ const TerritoryManager = () => { > @@ -313,7 +313,7 @@ const TerritoryManager = () => { >