feat: console fully wired to Doormile backend
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 && <Loader />}
|
||||
<Grid item xs={12} sx={{ mb: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center">
|
||||
<Typography variant="h3">Create Customer</Typography>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<MainCard sx={{ p: { xs: 1.5, md: 3 } }}>
|
||||
<Grid container spacing={{ xs: 2, md: 3 }}>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={{ xs: 2, md: 3 }}>
|
||||
{/* ===================================================== || Choose location || ===================================================== */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<LocationAutocomplete ref={locationRef} locaName={locaName} setAppId={setAppId} setLocoName={setLocoName} sx={{}} />
|
||||
</Grid>
|
||||
{/* ===================================================== || Choose client || ===================================================== */}
|
||||
<Grid item xs={12} md={6}>
|
||||
<Autocomplete
|
||||
fullWidth
|
||||
disabled={appId == 0}
|
||||
id="free-solo-demo"
|
||||
sx={{}}
|
||||
options={tenantlist || []}
|
||||
renderInput={(params) => <TextField {...params} label="Choose Client" focused />}
|
||||
onChange={(e, val, reason) => {
|
||||
if (val) {
|
||||
console.log('Client', val);
|
||||
gettenantlocations(val.tenantid);
|
||||
setTid(val.tenantid);
|
||||
} else {
|
||||
setClientinfo({});
|
||||
setTenantid('');
|
||||
}
|
||||
if (reason == 'clear') {
|
||||
}
|
||||
}}
|
||||
/>{' '}
|
||||
</Grid>
|
||||
|
||||
{/* ===================================================== || Name|| ===================================================== */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Name</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-last-name"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setFirstname(e.target.value)}
|
||||
value={firstname}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
{/* ===================================================== || Phone Number || ===================================================== */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-phone">Phone Number</InputLabel>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Select defaultValue="+1" disabled sx={{ cursor: 'not-allowed' }}>
|
||||
<MenuItem value="+1">+91</MenuItem>
|
||||
</Select>
|
||||
<TextField
|
||||
type="number"
|
||||
id="personal-phone"
|
||||
fullWidth
|
||||
placeholder="Phone Number"
|
||||
onChange={(e) => {
|
||||
if (e.target.value.toString().length <= 10) {
|
||||
setMobilenumber(e.target.value);
|
||||
}
|
||||
}}
|
||||
value={mobilenumber}
|
||||
autoComplete="off"
|
||||
// disabled
|
||||
sx={{ cursor: 'not-allowed' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
{/* ===================================================== || Email|| ===================================================== */}
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Email </InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
id="personal-email"
|
||||
placeholder="Email "
|
||||
onChange={(e) => setEmailaddress(e.target.value)}
|
||||
value={emailaddress}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
{/* ===================================================== || door no || ===================================================== */}
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Door No</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Door No"
|
||||
onChange={(e) => setDoorno(e.target.value)}
|
||||
value={doorno}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
{/* ===================================================== || Address || ===================================================== */}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email"> Address</InputLabel>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
id="addressAuto1"
|
||||
fullWidth
|
||||
value={inputValue2}
|
||||
onChange={(e) => {
|
||||
if (appId) {
|
||||
appId && setInputValue2(e.target.value);
|
||||
} else {
|
||||
OpenToast('Select Location First', 'warning', 3000);
|
||||
}
|
||||
}}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setInputValue2('');
|
||||
setPickCust({
|
||||
...pickCust,
|
||||
doorno: '',
|
||||
suburb: '',
|
||||
city: '',
|
||||
postcode: '',
|
||||
landmark: ''
|
||||
});
|
||||
setStartPoint({ latitude: 0, longitude: 0 });
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Location</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Location"
|
||||
onChange={(e) => setPickCust({ ...pickCust, suburb: e.target.value })}
|
||||
value={pickCust.suburb}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">City</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-zipcode"
|
||||
placeholder="City"
|
||||
onChange={(e) => setPickCust({ ...pickCust, city: e.target.value })}
|
||||
value={pickCust.city}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">State</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="State"
|
||||
onChange={(e) => setPickCust({ ...pickCust, state: e.target.value })}
|
||||
value={pickCust.state}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">Post Code</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
type="number"
|
||||
id="personal-zipcode"
|
||||
placeholder="Zipcode"
|
||||
onChange={(e) => setPickCust({ ...pickCust, postcode: e.target.value })}
|
||||
value={pickCust.postcode}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Landmark</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Landmark"
|
||||
onChange={(e) => setLandmark(e.target.value)}
|
||||
value={landmark}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Stack
|
||||
direction={{ xs: 'column', sm: 'row' }}
|
||||
justifyContent="flex-end"
|
||||
alignItems={{ xs: 'stretch', sm: 'center' }}
|
||||
spacing={2}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth={isMobile}
|
||||
onClick={() => {
|
||||
if (appId === '') {
|
||||
opentoast('Select Applocation ');
|
||||
} else if (tid === '') {
|
||||
opentoast('Select Tenant');
|
||||
} else if (firstname === '') {
|
||||
opentoast('Enter Name');
|
||||
} else if (mobilenumber === '') {
|
||||
opentoast('Enter Mobile Number ');
|
||||
} else if (address === '') {
|
||||
opentoast('Enter Address ');
|
||||
} else if (pickCust.city === '') {
|
||||
opentoast('Enter City ');
|
||||
} else if (pickCust.state === '') {
|
||||
opentoast('Enter State ');
|
||||
} else if (pickCust.suburb === '') {
|
||||
opentoast('Enter location ');
|
||||
} else if (pickCust.postcode === '') {
|
||||
opentoast('Enter Post Code ');
|
||||
} else if (landmark === '') {
|
||||
opentoast('Enter Land Mark ');
|
||||
} else if (pickCust.latitude === '') {
|
||||
opentoast('Invalid latitude ');
|
||||
} else if (pickCust.longitude === '') {
|
||||
opentoast('Invaiid Longitude ');
|
||||
} else {
|
||||
createprofile();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateCustomer;
|
||||
navigate('/nearle/customers');
|
||||
}, [navigate]);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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 && <Loader />}
|
||||
@@ -261,158 +78,34 @@ const Createclient = () => {
|
||||
</Grid>
|
||||
<MainCard contentSX={{ p: { xs: 1.5, md: 3 } }}>
|
||||
<Grid container spacing={isMobile ? 2 : 3}>
|
||||
{/* <Grid item xs={12} sm={4} >
|
||||
<MainCard title="Personal Information" sx={{ height: '100%' }}>
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={2.5} alignItems="center" sx={{ m: 3 }}>
|
||||
<FormLabel
|
||||
htmlFor="change-avtar"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
'&:hover .MuiBox-root': { opacity: 1 },
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
<Avatar alt="Avatar 1"
|
||||
src={avatar}
|
||||
sx={{ width: 76, height: 76 }} />
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
backgroundColor: theme.palette.mode === ThemeMode.DARK ? 'rgba(255, 255, 255, .75)' : 'rgba(0,0,0,.65)',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
opacity: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Stack spacing={0.5} alignItems="center">
|
||||
<CameraOutlined style={{ color: theme.palette.secondary.lighter, fontSize: '1.5rem' }} />
|
||||
<Typography sx={{ color: 'secondary.lighter' }} variant="caption">
|
||||
Upload
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</FormLabel>
|
||||
<TextField
|
||||
|
||||
type="file"
|
||||
accept="image/*"
|
||||
id="change-avtar"
|
||||
placeholder="Outlined"
|
||||
variant="outlined"
|
||||
sx={{ display: 'none' }}
|
||||
onChange={(e) => setSelectedImage(e.target.files?.[0])}
|
||||
/>
|
||||
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
|
||||
<Grid item xs={12}
|
||||
>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={12}
|
||||
>
|
||||
|
||||
</Grid>
|
||||
<Grid item xs={12}
|
||||
>
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}
|
||||
|
||||
>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-role-name">Role</InputLabel>
|
||||
<TextField fullWidth
|
||||
|
||||
id="personal-role-name" placeholder="Role Name" autoFocus
|
||||
onChange={(e) => setRole(e.target.value)}
|
||||
value={role}
|
||||
autoComplete='off'
|
||||
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</Grid> */}
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
// sm={8}
|
||||
>
|
||||
<MainCard
|
||||
// title="Contact Information"
|
||||
sx={{ height: '100%' }}
|
||||
contentSX={{ p: { xs: 1.5, md: 2.5 } }}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<MainCard sx={{ height: '100%' }} contentSX={{ p: { xs: 1.5, md: 2.5 } }}>
|
||||
<Grid container spacing={isMobile ? 2 : 3}>
|
||||
{/* <Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-first-name">Business Name</InputLabel>
|
||||
<TextField fullWidth
|
||||
id="personal-first-name" placeholder="Business Name" autoFocus
|
||||
onChange={(e) => setBusinessname(e.target.value)}
|
||||
value={businessname}
|
||||
autoComplete='off'
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Registration No</InputLabel>
|
||||
<TextField fullWidth
|
||||
id="personal-last-name" placeholder="Registration No"
|
||||
onChange={(e) => setBusinessno(e.target.value)}
|
||||
value={businessno}
|
||||
autoComplete='off'
|
||||
|
||||
/>
|
||||
</Stack>
|
||||
</Grid> */}
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-last-name">Admin Name</InputLabel>
|
||||
<InputLabel htmlFor="client-name">Client Name</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-last-name"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setFirstname(e.target.value)}
|
||||
value={firstname}
|
||||
id="client-name"
|
||||
placeholder="Client Name"
|
||||
onChange={(e) => setClientname(e.target.value)}
|
||||
value={clientname}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}></Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-phone">Phone Number</InputLabel>
|
||||
<InputLabel htmlFor="client-phone">Phone Number</InputLabel>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||
<Select defaultValue="+1" disabled sx={{ cursor: 'not-allowed' }}>
|
||||
<MenuItem value="+1">+91</MenuItem>
|
||||
</Select>
|
||||
<TextField
|
||||
type="number"
|
||||
id="personal-phone"
|
||||
// format="##########"
|
||||
// mask="_"
|
||||
id="client-phone"
|
||||
fullWidth
|
||||
// customInput={TextField}
|
||||
placeholder="Phone Number"
|
||||
// defaultValue="8654239581"
|
||||
// onBlur={() => { }}
|
||||
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' }}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Email Address</InputLabel>
|
||||
<InputLabel htmlFor="client-email">Email Address</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
id="client-email"
|
||||
placeholder="Email Address"
|
||||
onChange={(e) => setEmailaddress(e.target.value)}
|
||||
value={emailaddress}
|
||||
@@ -441,44 +131,12 @@ const Createclient = () => {
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="Street 110-B Kalians Bag, Dewan, M.P. New York"
|
||||
id="personal-address"
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
inputRef={materialRef}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Suburb</InputLabel>
|
||||
<InputLabel htmlFor="client-city">City</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Location"
|
||||
onChange={(e) => setSuburb(e.target.value)}
|
||||
value={suburb}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">City</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
// type='number'
|
||||
id="personal-zipcode"
|
||||
id="client-city"
|
||||
placeholder="City"
|
||||
onChange={(e) => setCity(e.target.value)}
|
||||
value={city}
|
||||
@@ -486,66 +144,6 @@ const Createclient = () => {
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">State</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="State"
|
||||
onChange={(e) => setState(e.target.value)}
|
||||
value={state}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-zipcode">Post Code</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="956754"
|
||||
type="number"
|
||||
id="personal-zipcode"
|
||||
placeholder="Zipcode"
|
||||
onChange={(e) => setZipcode(e.target.value)}
|
||||
value={zipcode}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Door No</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
// defaultValue="New York"
|
||||
id="personal-location"
|
||||
placeholder="Door No"
|
||||
onChange={(e) => setDoorno(e.target.value)}
|
||||
value={doorno}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-email">Landmark</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Landmark"
|
||||
onChange={(e) => setLandmark(e.target.value)}
|
||||
value={landmark}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
</Grid>
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in New Issue
Block a user