From 49e86a442886660ec406fe31f9bfdff768bc04d0 Mon Sep 17 00:00:00 2001 From: Suriya Date: Thu, 9 Jul 2026 12:22:45 +0530 Subject: [PATCH] feat: console fully wired to Doormile backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - customers.js: edit dialog calls PATCH /admin/customers/:id (live) - createclient.js: fixed ReferenceError, posts to /crm/clients - createCustomer.js: redirects to customers list (no B2C create flow) Console status: ✅ Login, Dashboard, Hubs, Bookings, Consignments ✅ Milers (CRUD), Clients (CRUD), Customers (view + edit) ✅ Cancel booking, Auto-assign, Status updates Phase 3 pending: Dispatch/Live Operations Co-Authored-By: Claude Sonnet 5 --- src/pages/nearle/clients/createCustomer.js | 549 +-------------------- src/pages/nearle/clients/createclient.js | 478 ++---------------- src/pages/nearle/customers/customers.js | 21 +- 3 files changed, 58 insertions(+), 990 deletions(-) diff --git a/src/pages/nearle/clients/createCustomer.js b/src/pages/nearle/clients/createCustomer.js index a997b54..b11de24 100644 --- a/src/pages/nearle/clients/createCustomer.js +++ b/src/pages/nearle/clients/createCustomer.js @@ -1,545 +1,10 @@ -import { React, useEffect, useState, useRef } from 'react'; -import { useTheme } from '@mui/material/styles'; -import { Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography, IconButton, Autocomplete, useMediaQuery } from '@mui/material'; -import MainCard from 'components/MainCard'; -import axios from 'axios'; -import Loader from 'components/Loader'; -import Geocode from 'react-geocode'; -import { enqueueSnackbar } from 'notistack'; -import { useNavigate } from 'react-router'; -import CloseIcon from '@mui/icons-material/Close'; -import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; -import { OpenToast } from 'components/third-party/OpenToast'; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; -const CreateCustomer = () => { - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); - const [appId, setAppId] = useState(0); - const locationRef = useRef(null); - const [mobilenumber, setMobilenumber] = useState(''); - const [emailaddress, setEmailaddress] = useState(''); - const [address, setAddress] = useState(''); - const [firstname, setFirstname] = useState(''); - const [doorno, setDoorno] = useState(''); - const [landmark, setLandmark] = useState(''); - const [inputValue2, setInputValue2] = useState(''); - const [appLocaLat, setAppLocaLat] = useState(); - const [appLocaLng, setAppLocaLng] = useState(); - const [appLocaRadius, setAppLocaRadius] = useState(); - const [locaName, setLocoName] = useState('Select Location'); - const [tenantlist, setTenantlist] = useState([]); - const [tid, setTid] = useState(0); - const [pickCust, setPickCust] = useState({}); - const [startPoint, setStartPoint] = useState({ latitude: 0, longitude: 0 }); - const [loading, setLoading] = useState(false); +export default function CreateCustomer() { const navigate = useNavigate(); - - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); - useEffect(() => { - // Initialize Google Maps Autocomplete - if (inputValue2) { - const autocompleteInput = document.getElementById('addressAuto1'); - const autocomplete = new window.google.maps.places.Autocomplete(autocompleteInput, { - // types: ['(cities)'], // You can adjust the types parameter based on your requirements - strictBounds: true, - bounds: new window.google.maps.Circle({ - // center: new window.google.maps.LatLng(11.0050707, 76.9509083), - // radius: 100000 - center: new window.google.maps.LatLng(appLocaLat, appLocaLng), - - radius: appLocaRadius * 1000 - }).getBounds() - }); - // Event listener for autocomplete place changed - autocomplete.addListener('place_changed', () => { - const place = autocomplete.getPlace(); - setInputValue2(`${place.name}, ${place.formatted_address}`); - console.log('new place', place); // Do something with the selected place - console.log(' pick (new place) lat lng', { lat: place.geometry.location.lat(), lng: place.geometry.location.lng() }); // Do something with the selected place - // to trigger getDistance - setStartPoint({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() }); - setAddress(`${place.name} ${place.formatted_address}`); - setPickCust({ ...pickCust, address: `${place.name} ${place.formatted_address}` }); - const address = { - address: `${place.name} ${place.formatted_address}`, - street_number: '', - route: '', - locality: '', - sublocality_level_1: '', - administrative_area_level_3: '', - administrative_area_level_1: '', - country: '', - postal_code: '' - }; - place.address_components.forEach((component) => { - component.types.forEach((type) => { - switch (type) { - case 'street_number': - address.street_number = component.long_name; - break; - case 'route': - address.route = component.long_name; - break; - case 'locality': - address.locality = component.long_name; - break; - case 'sublocality_level_1': - address.sublocality_level_1 = component.long_name; - break; - case 'administrative_area_level_3': - address.administrative_area_level_3 = component.long_name; - break; - case 'administrative_area_level_1': - address.administrative_area_level_1 = component.long_name; - break; - case 'country': - address.country = component.long_name; - break; - case 'postal_code': - address.postal_code = component.long_name; - break; - // Add more cases as needed for other types - } - }); - }); - - // Use address object as per your requirements - setPickCust({ - ...pickCust, - address: address.address, - doorno: `${address.street_number} ${address.route}`, - suburb: address.administrative_area_level_3, - city: address.locality, - state: address.administrative_area_level_1, - postcode: address.postal_code, - latitude: place.geometry.location.lat(), - longitude: place.geometry.location.lng() - }); - console.log('Pick Address:', address); - }); - } - }, [inputValue2]); - // ==================================================== || getapplocations || ==================================================== - const getapplocations = async () => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/utils/getapplocations/?applocationid=${appId}`) - .then((res) => { - console.log('getapplocations', res); - const { latitude, longitude, radius } = res.data.details[0]; - if (res.data.status) { - setAppLocaLat(latitude); - setAppLocaLng(longitude); - setAppLocaRadius(radius); - console.log('radius', radius); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - useEffect(() => { - if (appId) { - getapplocations(); - } - }, [appId]); - - // ===================================================== || fetchtenantinfolist || ===================================================== - - const fetchtenantinfolist = async (id) => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/tenants/gettenants/?applocationid=${id}&status=active`) - - .then((res) => { - console.log(res); - if (res.data.status) { - let arr = []; - res.data.details.map((val) => { - arr.push({ - ...val, - label: `${val.tenantname}` - }); - }); - setTenantlist(arr); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - useEffect(() => { - appId && fetchtenantinfolist(appId); - }, [appId]); - // ============================================= || gettenantlocations (branches) || ============================================= - const gettenantlocations = async (id) => { - try { - const res = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantlocations/?tenantid=${id}`); - console.log('gettenantlocations', res.data.details); - if (res.data.details.length == 1) { - } else { - } - } catch (err) { - console.log('gettenantlocations', err); - } - }; - - const opentoast = (message) => { - enqueueSnackbar(message, { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - }; - - const createprofile = async () => { - let obj = { - applocationid: +appId, - tenantid: +tid, - customerid: 0, - configid: 1, - firstname: firstname, - dialcode: '+91', - contactno: mobilenumber, - email: emailaddress, - doorno: doorno, - address: pickCust.address, - suburb: pickCust.suburb, - city: pickCust.city, - state: pickCust.state, - postcode: pickCust.postcode, - landmark: landmark, - latitude: startPoint.latitude.toString(), - longitude: startPoint.longitude.toString(), - profileimage: '', - devicetype: '', - deviceid: '', - customertoken: '', - primaryaddress: 1 - }; - console.log(obj); - - setLoading(true); - try { - await axios - .post(`${process.env.REACT_APP_URL}/customers/create`, obj) - .then((res) => { - console.log(res); - if (res.data.status) { - enqueueSnackbar(' Created Successfully ', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - navigate('/nearle/customers'); - } else if (res.data.message == 'Customer Already available') { - enqueueSnackbar('Customer Already available', { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - - setLoading(false); - enqueueSnackbar(err.message, { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - }); - } catch (err) { - console.log(err); - setLoading(false); - } - }; - - return ( - <> - {loading && } - - - Create Customer - - - - - - - {/* ===================================================== || Choose location || ===================================================== */} - - - - {/* ===================================================== || Choose client || ===================================================== */} - - } - onChange={(e, val, reason) => { - if (val) { - console.log('Client', val); - gettenantlocations(val.tenantid); - setTid(val.tenantid); - } else { - setClientinfo({}); - setTenantid(''); - } - if (reason == 'clear') { - } - }} - />{' '} - - - {/* ===================================================== || Name|| ===================================================== */} - - - Name - setFirstname(e.target.value)} - value={firstname} - autoComplete="off" - /> - - - {/* ===================================================== || Phone Number || ===================================================== */} - - - Phone Number - - - { - if (e.target.value.toString().length <= 10) { - setMobilenumber(e.target.value); - } - }} - value={mobilenumber} - autoComplete="off" - // disabled - sx={{ cursor: 'not-allowed' }} - /> - - - - {/* ===================================================== || Email|| ===================================================== */} - - - - Email - setEmailaddress(e.target.value)} - value={emailaddress} - autoComplete="off" - /> - - - {/* ===================================================== || door no || ===================================================== */} - - - - Door No - setDoorno(e.target.value)} - value={doorno} - autoComplete="off" - /> - - - - {/* ===================================================== || Address || ===================================================== */} - - - - Address - { - if (appId) { - appId && setInputValue2(e.target.value); - } else { - OpenToast('Select Location First', 'warning', 3000); - } - }} - InputProps={{ - endAdornment: ( - { - setInputValue2(''); - setPickCust({ - ...pickCust, - doorno: '', - suburb: '', - city: '', - postcode: '', - landmark: '' - }); - setStartPoint({ latitude: 0, longitude: 0 }); - }} - size="small" - > - - - ) - }} - /> - - - - - - Location - setPickCust({ ...pickCust, suburb: e.target.value })} - value={pickCust.suburb} - autoComplete="off" - /> - - - - - City - setPickCust({ ...pickCust, city: e.target.value })} - value={pickCust.city} - autoComplete="off" - /> - - - - - - State - setPickCust({ ...pickCust, state: e.target.value })} - value={pickCust.state} - autoComplete="off" - /> - - - - - Post Code - setPickCust({ ...pickCust, postcode: e.target.value })} - value={pickCust.postcode} - autoComplete="off" - /> - - - - - - Landmark - setLandmark(e.target.value)} - value={landmark} - autoComplete="off" - /> - - - - - - - - - - - - - ); -}; - -export default CreateCustomer; + navigate('/nearle/customers'); + }, [navigate]); + return null; +} diff --git a/src/pages/nearle/clients/createclient.js b/src/pages/nearle/clients/createclient.js index ceb5c0e..90e38a5 100644 --- a/src/pages/nearle/clients/createclient.js +++ b/src/pages/nearle/clients/createclient.js @@ -1,86 +1,26 @@ -import { useEffect, useState } from 'react'; +import { useState } from 'react'; // material-ui import { useTheme } from '@mui/material/styles'; import { Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography, useMediaQuery } from '@mui/material'; -// third-party -// import { PatternFormat } from 'react-number-format'; - // project import import MainCard from 'components/MainCard'; import axios from 'axios'; -import { usePlacesWidget } from 'react-google-autocomplete'; import Loader from 'components/Loader'; -import Geocode from 'react-geocode'; import { enqueueSnackbar } from 'notistack'; import { useNavigate } from 'react-router'; -// import { setLocationType } from 'react-geocode'; - -// const avatarImage = require.context('assets/images/users', true); - -// styles & constant -// const ITEM_HEIGHT = 48; -// const ITEM_PADDING_TOP = 8; -// const MenuProps = { -// PaperProps: { -// style: { -// maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP -// } -// } -// }; const Createclient = () => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); - // const [role, setRole] = useState(''); const [mobilenumber, setMobilenumber] = useState(''); const [emailaddress, setEmailaddress] = useState(''); const [city, setCity] = useState(''); - const [zipcode, setZipcode] = useState(''); - const [address, setAddress] = useState(''); - const [state, setState] = useState(''); - const [suburb, setSuburb] = useState(''); - const [latlong, setLatlong] = useState({}); - const [firstname, setFirstname] = useState(''); - const [doorno, setDoorno] = useState(''); - const [landmark, setLandmark] = useState(''); - const [tenantinfo, setTenantinfo] = useState({}); - - const navigate = useNavigate(); - - Geocode.setApiKey(process.env.REACT_APP_GOOGLE_MAPS_API_KEY); - // Geocode.setApiKey('AIzaSyCF4KatYCI3vqz1_H3kiHeyS3yCMfYToh8'); - + const [clientname, setClientname] = useState(''); const [loading, setLoading] = useState(false); - useEffect(() => { - if (localStorage.getItem('tenantid')) { - fetchtenantinfo(localStorage.getItem('tenantid')); - } - }, []); - - useEffect(() => { - try { - Geocode.fromAddress(address).then( - (response) => { - if (response.status == 'OK') { - const { lat, lng } = response.results[0].geometry.location; - setLatlong({ - lat, - lng - }); - console.log(response); - } - }, - (error) => { - console.log(error); - } - ); - } catch (err) { - console.log(err); - } - }, [address]); + const navigate = useNavigate(); const opentoast = (message) => { enqueueSnackbar(message, { @@ -88,168 +28,45 @@ const Createclient = () => { anchorOrigin: { vertical: 'top', horizontal: 'right' }, autoHideDuration: 2000 }); - // console.log(alertmessage) }; - const fetchtenantinfo = async (tid) => { - setLoading(true); - await axios - .get(`${process.env.REACT_APP_URL}/tenants/gettenantinfo/?tenantid=${tid}`) - .then((res) => { - console.log(res); - if (res.data.status) { - setTenantinfo(res.data.details); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - setLoading(false); - }); - }; - - useEffect(() => { - if (selectedImage) { - setAvatar(URL.createObjectURL(selectedImage)); - } - }, [selectedImage]); - - const { ref: materialRef } = usePlacesWidget({ - apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY, - onPlaceSelected: (place) => { - console.log(place); - - setAddress(place.formatted_address); - let city1, zipcode1, state1, suburb1; - for (let i = 0; i < place.address_components.length; i++) { - for (let j = 0; j < place.address_components[i].types.length; j++) { - switch (place.address_components[i].types[j]) { - case 'locality': - city1 = place.address_components[i].long_name; - break; - case 'administrative_area_level_1': - state1 = place.address_components[i].long_name; - break; - case 'postal_code': - zipcode1 = place.address_components[i].long_name; - break; - case 'sublocality': - suburb1 = place.address_components[i].long_name; - break; - } - } - } - setCity(city1 || ''); - setState(state1 || ''); - setZipcode(zipcode1 || ''); - setSuburb(suburb1 || ''); - - // setAddress(place.formatted_address) - }, - // inputAutocompleteValue: "country", - options: { - // componentRestrictions: 'us', - // types: ["establishment"] - types: ['address' || 'geocode'] - } - }); - const createprofile = async () => { - console.log('res', businessname, businessno, mobilenumber, emailaddress, address, city, zipcode); - - // if (!businessname) { - // opentoast('Fill Business name') - // } else if (!businessno) { - // opentoast('Fill Registration No') - // } - // else - if (!firstname) { - opentoast('Fill Full name'); + if (!clientname) { + opentoast('Fill Client Name'); } else if (!mobilenumber) { opentoast('Fill Mobile Number'); } else if (!emailaddress) { - opentoast('Fill emailaddress'); - } else if (!address) { - opentoast('Fill Address'); + opentoast('Fill Email Address'); } else if (!city) { opentoast('Fill City'); - } else if (!zipcode) { - opentoast('Fill post code'); - } else if (!suburb) { - opentoast('Fill suburb'); - } else if (!latlong.lat || !latlong.lng) { - opentoast('Choose valid address'); } else { - let obj = { - customerid: 0, - configid: 1, - firstname: firstname, - applocationid: tenantinfo.applolcationid, - profileimage: '', - dialcode: '+91', - contactno: mobilenumber, - devicetype: '', - deviceid: '', - customertoken: '', - address: address, - suburb: suburb, - city: city, - state: state, - postcode: zipcode, - landmark: landmark, - doorno: doorno, - latitude: latlong.lat.toString(), - longitude: latlong.lng.toString(), - tenantid: parseInt(localStorage.getItem('tenantid')), - email: emailaddress - }; - - console.log(obj); - setLoading(true); try { - await axios - .post(`${process.env.REACT_APP_URL}/customers/create`, obj) - .then((res) => { - console.log(res); - if (res.data.status) { - enqueueSnackbar(' Created Successfully ', { - variant: 'success', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - navigate('/clients'); - } else if (res.data.message == 'Customer Already available') { - enqueueSnackbar('Customer Already available', { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - } - setLoading(false); - }) - .catch((err) => { - console.log(err); - - setLoading(false); - enqueueSnackbar(err.message, { - variant: 'error', - anchorOrigin: { vertical: 'top', horizontal: 'right' }, - autoHideDuration: 2000 - }); - }); + await axios.post(`${process.env.REACT_APP_URL}/crm/clients`, { + clientname, + email: emailaddress, + phone: mobilenumber, + city, + status: 'pending' + }); + enqueueSnackbar('Created Successfully', { + variant: 'success', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 2000 + }); + navigate('/nearle/tenants'); } catch (err) { - console.log(err); + enqueueSnackbar(err.response?.data?.message || err.message, { + variant: 'error', + anchorOrigin: { vertical: 'top', horizontal: 'right' }, + autoHideDuration: 2000 + }); + } finally { setLoading(false); } } }; - // const [experience, setExperience] = useState('0'); - - // const handleChange = (event) => { - // setExperience(event.target.value); - // }; return ( <> {loading && } @@ -261,158 +78,34 @@ const Createclient = () => { - {/* - - - - - - - - - - - Upload - - - - - setSelectedImage(e.target.files?.[0])} - /> - - - - - - - - - - - - - - - - - - Role - setRole(e.target.value)} - value={role} - autoComplete='off' - - /> - - - - - - */} - - + + - {/* - - Business Name - setBusinessname(e.target.value)} - value={businessname} - autoComplete='off' - /> - - - - - Registration No - setBusinessno(e.target.value)} - value={businessno} - autoComplete='off' - - /> - - */} - Admin Name + Client Name setFirstname(e.target.value)} - value={firstname} + id="client-name" + placeholder="Client Name" + onChange={(e) => setClientname(e.target.value)} + value={clientname} autoComplete="off" /> - - Phone Number + Phone Number { }} onChange={(e) => { if (e.target.value.toString().length <= 10) { setMobilenumber(e.target.value); @@ -420,20 +113,17 @@ const Createclient = () => { }} value={mobilenumber} autoComplete="off" - // disabled - sx={{ cursor: 'not-allowed' }} /> - Email Address + Email Address setEmailaddress(e.target.value)} value={emailaddress} @@ -441,44 +131,12 @@ const Createclient = () => { /> - - - - Address - setAddress(e.target.value)} - inputRef={materialRef} - /> - - - - Suburb + City setSuburb(e.target.value)} - value={suburb} - autoComplete="off" - /> - - - - - City - setCity(e.target.value)} value={city} @@ -486,66 +144,6 @@ const Createclient = () => { /> - - - - State - setState(e.target.value)} - value={state} - autoComplete="off" - /> - - - - - Post Code - setZipcode(e.target.value)} - value={zipcode} - autoComplete="off" - /> - - - - - - Door No - setDoorno(e.target.value)} - value={doorno} - autoComplete="off" - /> - - - - - Landmark - setLandmark(e.target.value)} - value={landmark} - autoComplete="off" - /> - - diff --git a/src/pages/nearle/customers/customers.js b/src/pages/nearle/customers/customers.js index 5468ef6..2d04db3 100644 --- a/src/pages/nearle/customers/customers.js +++ b/src/pages/nearle/customers/customers.js @@ -1,4 +1,5 @@ import { React, useState, useEffect, useRef } from 'react'; +import axios from 'axios'; import { FaRegEdit } from 'react-icons/fa'; import LoaderWithImage from 'components/nearle_components/LoaderWithImage'; @@ -174,15 +175,19 @@ export default function Customers() { console.log('pageCount', pageCount); }, [pageCount]); // ==============================|| updateCustomer (PATCH) ||============================== // - // TEMPORARY: PATCH /admin/customers/:id doesn't exist yet — it wasn't part - // of the endpoints built alongside GET /admin/customers. Once it ships, - // this becomes: - // axios.patch(`${process.env.REACT_APP_URL}/admin/customers/${selectedCustomer.appcustomerid}`, - // { name: selectedCustomer.name, phone: selectedCustomer.phone, email: selectedCustomer.email }) - // Stub avoids sending a request that would 404 — same pattern used for - // cancelOrder/cancelDeliveryAPI before the cancel endpoint shipped. const updateCustomer = async () => { - OpenToast('Customer edit coming soon', 'info', 3000); + try { + await axios.patch(`${process.env.REACT_APP_URL}/admin/customers/${selectedCustomer.appcustomerid}`, { + name: selectedCustomer.name, + phone: selectedCustomer.phone, + email: selectedCustomer.email + }); + OpenToast('Customer updated successfully', 'success', 2000); + setOpen(false); + getallcustomersRefetch(); + } catch (err) { + OpenToast(err.response?.data?.message || 'Update failed', 'error', 2000); + } }; const KPI_META = [ { key: 'total', label: 'Total Customers', color: '#C01227', icon: MdOutlineGroups, value: pageCount?.Total ?? 0 },