Migrate console off jupiter.nearle.app to the Doormile Express API
Retires REACT_APP_URL/URL2/URL3 in favor of REACT_APP_DOORMILE_URL across every page (orders, deliveries, riders, tenants, pricing, profile, reports, dispatch). Fixes several field-mapping and envelope-check bugs found along the way, most notably that /admin/milers/:id routes (block, assign-vehicle, edit, notify) key off milerprofileid, not userid, and that a miler's real fields are phone/availabilitystatus/displayname, not contactno/status/ firstname+lastname (confirmed against a live read-only session). Also fixes several silent-failure bugs uncovered during that audit: order creation and order cancellation showed a success toast but gave no feedback at all on failure (createorder1.js had a dead notifyadmin() call that left the loading spinner stuck forever on every failed submit), and Tenants.js's pricing/profile updates never surfaced a failed response to the operator. The AI dispatch optimiser (routes.workolik.com/routemate.workolik.com) and its jupiter.nearle.app delivery-commit call remain untouched by design — separate solver service with no equivalent in the new API.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import React, { useEffect, useState, useRef, Fragment } from 'react';
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Loader from 'components/Loader';
|
||||
import { Empty } from 'antd';
|
||||
@@ -19,7 +18,6 @@ import {
|
||||
IconButton,
|
||||
Box,
|
||||
Grid,
|
||||
Autocomplete,
|
||||
TextField,
|
||||
Tooltip,
|
||||
Collapse,
|
||||
@@ -39,6 +37,8 @@ import {
|
||||
Skeleton,
|
||||
Avatar,
|
||||
Paper,
|
||||
Select,
|
||||
MenuItem,
|
||||
useMediaQuery
|
||||
} from '@mui/material';
|
||||
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
|
||||
@@ -65,7 +65,12 @@ import {
|
||||
MdOutlinePendingActions,
|
||||
MdOutlineCancel,
|
||||
MdMyLocation,
|
||||
MdPersonPin
|
||||
MdPersonPin,
|
||||
MdPlace,
|
||||
MdAdd,
|
||||
MdEdit,
|
||||
MdDeleteOutline,
|
||||
MdPeopleAlt
|
||||
} from 'react-icons/md';
|
||||
import { LuMail } from 'react-icons/lu';
|
||||
import { BiUser } from 'react-icons/bi';
|
||||
@@ -73,8 +78,21 @@ import LocationAutocomplete from 'components/nearle_components/LocationAutocompl
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
import PageHeader from 'components/nearle_components/PageHeader';
|
||||
import StatCard from 'components/nearle_components/StatCard';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { getalltenants, gettenantsummary } from 'pages/api/api';
|
||||
import {
|
||||
getAdminPricing,
|
||||
getDoormilePricing,
|
||||
createAdminPricing,
|
||||
updateAdminTenant,
|
||||
getTenantLocations,
|
||||
createTenantLocation,
|
||||
updateTenantLocation,
|
||||
getTenantCustomers,
|
||||
createTenantCustomer,
|
||||
updateTenantCustomer,
|
||||
deleteTenantCustomer
|
||||
} from 'pages/api/doormileApi';
|
||||
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
|
||||
|
||||
// ============================================================================
|
||||
@@ -103,24 +121,6 @@ const soft = (c) => a(c, '18');
|
||||
const ring = (c) => a(c, '26');
|
||||
const edge = (c) => a(c, '55');
|
||||
|
||||
const pillFieldSx = (color) => ({
|
||||
cursor: 'pointer',
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: '10px',
|
||||
bgcolor: '#ffffff',
|
||||
fontWeight: 600,
|
||||
color: DT.textPrimary,
|
||||
paddingRight: '8px',
|
||||
cursor: 'pointer',
|
||||
transition: 'border-color 0.15s, box-shadow 0.15s, background-color 0.2s',
|
||||
'& fieldset': { borderColor: '#e2e8f0', borderWidth: 1 },
|
||||
'&:hover fieldset': { borderColor: '#cbd5e1' },
|
||||
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(color)}` },
|
||||
'&.Mui-focused fieldset': { borderColor: '#662582', borderWidth: 1.5 }
|
||||
},
|
||||
'& .MuiAutocomplete-endAdornment .MuiSvgIcon-root': { color: color }
|
||||
});
|
||||
|
||||
// Status palette — drives the pill tabs and per-row badges.
|
||||
const STATUS_META = {
|
||||
active: { label: 'Active', color: '#10b981', icon: MdCheckCircle, statusKey: 'active' },
|
||||
@@ -168,6 +168,370 @@ const AccentAvatar = ({ color, selected, size = 24, children }) => (
|
||||
</Avatar>
|
||||
);
|
||||
|
||||
// ==============================|| Tenant locations (branches/sites) dialog ||============================== //
|
||||
// Self-contained — deliberately does not touch the existing openRowIndex1/2 +
|
||||
// Tabs collapse machinery above, which is fragile legacy state. GET/POST/PUT
|
||||
// /admin/tenants/:id/locations + /admin/tenantlocations/:id per
|
||||
// jupiter2doormile.md §2.4 — no location UI existed anywhere before this.
|
||||
const emptyLocationForm = {
|
||||
locationid: null,
|
||||
locationname: '',
|
||||
address: '',
|
||||
city: '',
|
||||
state: '',
|
||||
pincode: '',
|
||||
isprimary: false,
|
||||
status: 'active'
|
||||
};
|
||||
|
||||
const TenantLocationsDialog = ({ tenant, open, onClose }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [form, setForm] = useState(emptyLocationForm);
|
||||
const [formOpen, setFormOpen] = useState(false);
|
||||
|
||||
const { data: locations = [], isLoading } = useQuery({
|
||||
queryKey: ['tenant-locations', tenant?.tenantid],
|
||||
queryFn: () => getTenantLocations(tenant.tenantid),
|
||||
enabled: !!tenant?.tenantid && open
|
||||
});
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (payload) =>
|
||||
payload.locationid ? updateTenantLocation(payload.locationid, payload.data) : createTenantLocation(tenant.tenantid, payload.data),
|
||||
onSuccess: (res) => {
|
||||
if (res.success) {
|
||||
enqueueSnackbar(form.locationid ? 'Location updated' : 'Location added', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['tenant-locations', tenant?.tenantid] });
|
||||
setForm(emptyLocationForm);
|
||||
setFormOpen(false);
|
||||
} else {
|
||||
enqueueSnackbar(res.message || 'Failed to save location', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
enqueueSnackbar(err.response?.data?.message || err.message || 'Failed to save location', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
if (!form.locationname || !form.address) {
|
||||
enqueueSnackbar('Fill Location Name and Address', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { locationid, ...data } = form;
|
||||
saveMutation.mutate({ locationid, data });
|
||||
};
|
||||
|
||||
const openEdit = (loc) => {
|
||||
setForm({
|
||||
locationid: loc.locationid,
|
||||
locationname: loc.locationname || '',
|
||||
address: loc.address || '',
|
||||
city: loc.city || '',
|
||||
state: loc.state || '',
|
||||
pincode: loc.pincode || '',
|
||||
isprimary: !!loc.isprimary,
|
||||
status: loc.status || 'active'
|
||||
});
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700 }}>
|
||||
Locations · {tenant?.tenantname}
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{isLoading ? (
|
||||
<Typography variant="body2" sx={{ color: DT.textSecondary, py: 2 }}>
|
||||
Loading locations…
|
||||
</Typography>
|
||||
) : locations.length === 0 && !formOpen ? (
|
||||
<Empty description="No locations added yet" />
|
||||
) : (
|
||||
<Stack spacing={1.5}>
|
||||
{locations.map((loc) => (
|
||||
<Paper key={loc.locationid} variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: 700 }}>
|
||||
{loc.locationname} {loc.isprimary && '· Primary'}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
{[loc.address, loc.city, loc.state, loc.pincode].filter(Boolean).join(', ')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton size="small" onClick={() => openEdit(loc)}>
|
||||
<MdEdit size={14} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{formOpen && (
|
||||
<Grid container spacing={1.5} sx={{ mt: 1.5, pt: 1.5, borderTop: `1px solid ${DT.divider}` }}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Location Name"
|
||||
value={form.locationname}
|
||||
onChange={(e) => setForm((f) => ({ ...f, locationname: e.target.value }))}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Pincode" value={form.pincode} onChange={(e) => setForm((f) => ({ ...f, pincode: e.target.value }))} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField fullWidth label="Address" value={form.address} onChange={(e) => setForm((f) => ({ ...f, address: e.target.value }))} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="City" value={form.city} onChange={(e) => setForm((f) => ({ ...f, city: e.target.value }))} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="State" value={form.state} onChange={(e) => setForm((f) => ({ ...f, state: e.target.value }))} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
{formOpen ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setFormOpen(false);
|
||||
setForm(emptyLocationForm);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
{form.locationid ? 'Save Changes' : 'Add Location'}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
<Button variant="contained" startIcon={<MdAdd size={16} />} onClick={() => setFormOpen(true)}>
|
||||
Add Location
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
// ==============================|| Tenant customers dialog ||============================== //
|
||||
// GET /admin/tenantcustomers has no documented tenantid filter (unlike
|
||||
// locations, which are nested under /admin/tenants/:id/locations) — filtered
|
||||
// client-side here the same way fetchclientpricelist already filters
|
||||
// /admin/pricing client-side, since neither endpoint accepts the param.
|
||||
// createclient.js used to (incorrectly) create tenant customers instead of
|
||||
// tenants; now that it correctly creates tenants, this dialog is the only
|
||||
// place left that can create one.
|
||||
const emptyCustomerForm = { id: null, firstname: '', lastname: '', phone: '', email: '' };
|
||||
|
||||
// GET /admin/tenantcustomers doesn't document its id field name either — same
|
||||
// defensive fallback as customers.js's custId() rather than assuming `id`.
|
||||
const tenantCustId = (row) => row.id ?? row.tenantcustomerid ?? row.customerid;
|
||||
|
||||
const TenantCustomersDialog = ({ tenant, open, onClose }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const [form, setForm] = useState(emptyCustomerForm);
|
||||
const [formOpen, setFormOpen] = useState(false);
|
||||
|
||||
const { data: allCustomers = [], isLoading } = useQuery({
|
||||
queryKey: ['tenant-customers-all'],
|
||||
queryFn: getTenantCustomers,
|
||||
enabled: open
|
||||
});
|
||||
|
||||
const customers = (allCustomers || []).filter((c) => !tenant?.tenantid || c.tenantid === tenant.tenantid);
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: (payload) => (payload.id ? updateTenantCustomer(payload.id, payload.data) : createTenantCustomer(payload.data)),
|
||||
onSuccess: (res) => {
|
||||
if (res.success) {
|
||||
enqueueSnackbar(form.id ? 'Customer updated' : 'Customer added', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['tenant-customers-all'] });
|
||||
setForm(emptyCustomerForm);
|
||||
setFormOpen(false);
|
||||
} else {
|
||||
enqueueSnackbar(res.message || 'Failed to save customer', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
enqueueSnackbar(err.response?.data?.message || err.message || 'Failed to save customer', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id) => deleteTenantCustomer(id),
|
||||
onSuccess: (res) => {
|
||||
if (res.success) {
|
||||
enqueueSnackbar('Customer deleted', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ['tenant-customers-all'] });
|
||||
} else {
|
||||
enqueueSnackbar(res.message || 'Failed to delete customer', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
enqueueSnackbar(err.response?.data?.message || err.message || 'Failed to delete customer', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const handleSave = () => {
|
||||
if (!form.firstname || !form.phone) {
|
||||
enqueueSnackbar('Fill Name and Phone', {
|
||||
variant: 'error',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { id, ...data } = form;
|
||||
saveMutation.mutate({ id, data });
|
||||
};
|
||||
|
||||
const openEdit = (row) => {
|
||||
setForm({
|
||||
id: tenantCustId(row),
|
||||
firstname: row.firstname || '',
|
||||
lastname: row.lastname || '',
|
||||
phone: row.phone || '',
|
||||
email: row.email || ''
|
||||
});
|
||||
setFormOpen(true);
|
||||
};
|
||||
|
||||
const handleDelete = (row) => {
|
||||
if (window.confirm(`Delete customer "${row.firstname}"?`)) deleteMutation.mutate(tenantCustId(row));
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||
<DialogTitle sx={{ fontWeight: 700 }}>Customers · {tenant?.tenantname}</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
{isLoading ? (
|
||||
<Typography variant="body2" sx={{ color: DT.textSecondary, py: 2 }}>
|
||||
Loading customers…
|
||||
</Typography>
|
||||
) : customers.length === 0 && !formOpen ? (
|
||||
<Empty description="No customers yet" />
|
||||
) : (
|
||||
<Stack spacing={1.5}>
|
||||
{customers.map((c) => (
|
||||
<Paper key={tenantCustId(c)} variant="outlined" sx={{ p: 1.5, borderRadius: 2 }}>
|
||||
<Stack direction="row" justifyContent="space-between" alignItems="flex-start">
|
||||
<Box>
|
||||
<Typography sx={{ fontWeight: 700 }}>
|
||||
{c.firstname} {c.lastname}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||||
{[c.phone, c.email].filter(Boolean).join(' · ')}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack direction="row" spacing={0.5}>
|
||||
<IconButton size="small" onClick={() => openEdit(c)}>
|
||||
<MdEdit size={14} />
|
||||
</IconButton>
|
||||
<IconButton size="small" onClick={() => handleDelete(c)} sx={{ color: '#ef4444' }}>
|
||||
<MdDeleteOutline size={14} />
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Paper>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
{formOpen && (
|
||||
<Grid container spacing={1.5} sx={{ mt: 1.5, pt: 1.5, borderTop: `1px solid ${DT.divider}` }}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="First Name" value={form.firstname} onChange={(e) => setForm((f) => ({ ...f, firstname: e.target.value }))} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Last Name" value={form.lastname} onChange={(e) => setForm((f) => ({ ...f, lastname: e.target.value }))} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Phone" value={form.phone} onChange={(e) => setForm((f) => ({ ...f, phone: e.target.value }))} />
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<TextField fullWidth label="Email" value={form.email} onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</DialogContent>
|
||||
<DialogActions sx={{ px: 3, pb: 2 }}>
|
||||
{formOpen ? (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setFormOpen(false);
|
||||
setForm(emptyCustomerForm);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
{form.id ? 'Save Changes' : 'Add Customer'}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
<Button variant="contained" startIcon={<MdAdd size={16} />} onClick={() => setFormOpen(true)}>
|
||||
Add Customer
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
// ==============================|| Starts here||============================== //
|
||||
const Clients1 = () => {
|
||||
const textFieldRef = useRef(null);
|
||||
@@ -189,7 +553,7 @@ const Clients1 = () => {
|
||||
const [status, setstatus] = useState('active');
|
||||
const [selectedCustomer, setSelectedCustomer] = useState({}); // to edit
|
||||
const [dialogopen, setDialogopen] = useState(false);
|
||||
const [appPricing, setAppPricing] = useState([]);
|
||||
const [, setAppPricing] = useState([]);
|
||||
const [selectedPricing, setSelectedPricing] = useState({});
|
||||
const [isPrice, setIsprice] = useState(true);
|
||||
const [city, setCity] = useState('');
|
||||
@@ -197,7 +561,6 @@ const Clients1 = () => {
|
||||
const [latlong, setLatlong] = useState({});
|
||||
const [editClient, setEditClient] = useState({});
|
||||
const [page, setPage] = useState(0);
|
||||
const [tabStatus, setTabStatus] = useState('Active');
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
@@ -212,7 +575,6 @@ const Clients1 = () => {
|
||||
};
|
||||
|
||||
const handleChange = (event, newValue) => {
|
||||
setTabStatus(newValue == 0 ? 'Active' : newValue == 1 ? 'Pending' : 'InActive');
|
||||
setValue0(newValue);
|
||||
setSearchword('');
|
||||
setPage(0);
|
||||
@@ -228,6 +590,8 @@ const Clients1 = () => {
|
||||
|
||||
const [openRowIndex1, setOpenRowIndex1] = useState(null); // Initially no row is open for collapsible section 1
|
||||
const [openRowIndex2, setOpenRowIndex2] = useState(null); // Initially no row is open for collapsible section 2
|
||||
const [locationsDialogTenant, setLocationsDialogTenant] = useState(null);
|
||||
const [customersDialogTenant, setCustomersDialogTenant] = useState(null);
|
||||
// Handle toggling for collapsible section 1
|
||||
const handleCollapseToggle1 = (rowIndex) => {
|
||||
setOpenRowIndex1((prevIndex) => (prevIndex === rowIndex ? null : rowIndex));
|
||||
@@ -348,18 +712,14 @@ const Clients1 = () => {
|
||||
|
||||
/* ============================================= || fetchclientpricelist || ============================================= */
|
||||
|
||||
// /admin/pricing has no tenantid query param documented — filter client-side.
|
||||
const fetchclientpricelist = async () => {
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL}/tenants/getpricinglist/?tenantid=${selectedTenid}`)
|
||||
.then((res) => {
|
||||
console.log('getpricinglist', res);
|
||||
if (res.data.status) {
|
||||
setClientpricelist(res.data.details);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
try {
|
||||
const pricing = await getAdminPricing();
|
||||
setClientpricelist((pricing || []).filter((p) => p.tenantid === selectedTenid));
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
selectedTenid && fetchclientpricelist();
|
||||
@@ -368,9 +728,8 @@ const Clients1 = () => {
|
||||
/* ============================================= || fetchTenanatPricing || ============================================= */
|
||||
const fetchTenanatPricing = async (id) => {
|
||||
try {
|
||||
let tenantPricing = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantpricing/?tenantid=${id}`);
|
||||
console.log('tenantPricing', tenantPricing.data.details);
|
||||
setTenanatPricing(tenantPricing.data.details);
|
||||
const pricing = await getAdminPricing();
|
||||
setTenanatPricing((pricing || []).filter((p) => p.tenantid === id));
|
||||
} catch (error) {
|
||||
console.log('fetchTenanatPricing', error);
|
||||
}
|
||||
@@ -382,44 +741,40 @@ const Clients1 = () => {
|
||||
|
||||
const tenantupdate = async (tenid) => {
|
||||
setisloader(true);
|
||||
let targetId;
|
||||
let updateData;
|
||||
if (tenid == -1) {
|
||||
updateData = {
|
||||
tenantid: selectedTenid,
|
||||
approved: 1
|
||||
};
|
||||
targetId = selectedTenid;
|
||||
updateData = { approved: 1 };
|
||||
} else {
|
||||
updateData = { tenantid: tenid, status: status === 'active' ? 'InActive' : 'Active' };
|
||||
targetId = tenid;
|
||||
updateData = { status: status === 'active' ? 'InActive' : 'Active' };
|
||||
}
|
||||
|
||||
await axios
|
||||
.put(`${process.env.REACT_APP_URL}/tenants/update`, updateData)
|
||||
.then((res) => {
|
||||
if (res.data.status) {
|
||||
opentoast(
|
||||
value0 == 0 ? 'Inactivated Successfully' : value0 == 1 ? 'Approved Successfully' : 'Activate Successfully',
|
||||
'success',
|
||||
2000
|
||||
);
|
||||
getalltenantsRefetch();
|
||||
summaryDataRefetch();
|
||||
setisloader(false);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
opentoast(err.message, 'error', 1500);
|
||||
setisloader(false);
|
||||
});
|
||||
// }
|
||||
try {
|
||||
const res = await updateAdminTenant(targetId, updateData);
|
||||
if (res.success) {
|
||||
opentoast(
|
||||
value0 == 0 ? 'Inactivated Successfully' : value0 == 1 ? 'Approved Successfully' : 'Activate Successfully',
|
||||
'success',
|
||||
2000
|
||||
);
|
||||
getalltenantsRefetch();
|
||||
summaryDataRefetch();
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
opentoast(err.message, 'error', 1500);
|
||||
} finally {
|
||||
setisloader(false);
|
||||
}
|
||||
};
|
||||
/* ============================================= || getAppPricing || ============================================= */
|
||||
// /admin/doormile-pricing has no zone query param documented — filter client-side.
|
||||
const getAppPricing = async (id) => {
|
||||
console.log('id', id);
|
||||
try {
|
||||
let appPricingRes = await axios.get(`${process.env.REACT_APP_URL}/utils/getapppricing/?applocationid=${id}`);
|
||||
console.log('appPricingRes', appPricingRes.data.details);
|
||||
setAppPricing(appPricingRes.data.details);
|
||||
const pricing = await getDoormilePricing();
|
||||
setAppPricing((pricing || []).filter((p) => !id || p.applocationid === id));
|
||||
} catch (error) {
|
||||
console.log('appPricingRes', error);
|
||||
}
|
||||
@@ -427,55 +782,54 @@ const Clients1 = () => {
|
||||
/* ============================================= || createpricing || ============================================= */
|
||||
const createpricing = async () => {
|
||||
setisloader(true);
|
||||
await axios
|
||||
.post(`${process.env.REACT_APP_URL}/tenants/createpricing`, {
|
||||
tenantpricingid: 0,
|
||||
try {
|
||||
const res = await createAdminPricing({
|
||||
applocationid: appId,
|
||||
pricingid: selectedPricing.pricingid,
|
||||
tenantid: selectedTenid,
|
||||
pricingdate: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
configid: selectedPricing.configid,
|
||||
pricingtypeid: selectedPricing.pricingtypeid,
|
||||
slab: selectedPricing.slab,
|
||||
baseprice: +selectedPricing.baseprice,
|
||||
priceperkm: +selectedPricing.priceperkm,
|
||||
minkm: +selectedPricing.minkm,
|
||||
maxkm: +selectedPricing.maxkm,
|
||||
orders: +selectedPricing.minorder,
|
||||
othercharges: 0
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.data.status) {
|
||||
enqueueSnackbar('Price Created Successfully', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
dialogclose();
|
||||
setSelectedPricing({});
|
||||
tenantupdate(-1);
|
||||
fetchclientpricelist();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
vehicletype: selectedPricing.vehicletype || 'Bike',
|
||||
baseprice: +selectedPricing.baseprice || 0,
|
||||
baseweight: +selectedPricing.baseweight || 0,
|
||||
priceperkm: +selectedPricing.priceperkm || 0,
|
||||
priceperkg: +selectedPricing.priceperkg || 0,
|
||||
basedistance: +selectedPricing.basedistance || 0,
|
||||
handlingcharges: +selectedPricing.handlingcharges || 0,
|
||||
effectivefrom: dayjs().format('YYYY-MM-DD'),
|
||||
currency: 'INR',
|
||||
priority: 1,
|
||||
status: 'active'
|
||||
});
|
||||
if (res.success) {
|
||||
enqueueSnackbar('Price Created Successfully', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
dialogclose();
|
||||
setSelectedPricing({});
|
||||
tenantupdate(-1);
|
||||
fetchclientpricelist();
|
||||
} else {
|
||||
opentoast(res.message || 'Failed to save pricing', 'error', 2000);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
opentoast(err.response?.data?.message || err.message || 'Failed to save pricing', 'error', 2000);
|
||||
}
|
||||
};
|
||||
/* ============================================= || updateClient || ============================================= */
|
||||
const updateClient = async () => {
|
||||
try {
|
||||
const arr = { ...editClient, tenantid: selectedTenid };
|
||||
|
||||
console.log('updateClient', arr);
|
||||
const updateRes = await axios.put(`${process.env.REACT_APP_URL}/tenants/update`, arr);
|
||||
console.log('updateClient', updateRes.data.message);
|
||||
if (updateRes.data.status) {
|
||||
opentoast(updateRes.data.message, 'success', 1500);
|
||||
const updateRes = await updateAdminTenant(selectedTenid, editClient);
|
||||
if (updateRes.success) {
|
||||
opentoast(updateRes.message || 'Updated Successfully', 'success', 1500);
|
||||
} else {
|
||||
opentoast(updateRes.message || 'Update Failed', 'error', 1500);
|
||||
}
|
||||
getalltenantsRefetch();
|
||||
summaryDataRefetch();
|
||||
} catch (err) {
|
||||
console.log('updateClient', err);
|
||||
opentoast(err.response?.data?.message || err.message || 'Update Failed', 'error', 1500);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -912,6 +1266,34 @@ const Clients1 = () => {
|
||||
{openRowIndex1 === index ? <EyeInvisibleOutlined style={{ fontSize: 14 }} /> : <EyeOutlined style={{ fontSize: 14 }} />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Locations" placement="top">
|
||||
<IconButton
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: soft('#0ea5e9'),
|
||||
color: '#0ea5e9',
|
||||
border: `1px solid ${edge('#0ea5e9')}`,
|
||||
'&:hover': { bgcolor: '#0ea5e9', color: '#fff' }
|
||||
}}
|
||||
onClick={() => setLocationsDialogTenant(row)}
|
||||
>
|
||||
<MdPlace size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title="Customers" placement="top">
|
||||
<IconButton
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: soft('#14b8a6'),
|
||||
color: '#14b8a6',
|
||||
border: `1px solid ${edge('#14b8a6')}`,
|
||||
'&:hover': { bgcolor: '#14b8a6', color: '#fff' }
|
||||
}}
|
||||
onClick={() => setCustomersDialogTenant(row)}
|
||||
>
|
||||
<MdPeopleAlt size={14} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{value0 !== 1 && (
|
||||
<Tooltip title={openRowIndex2 === index ? 'Close' : 'Edit'} placement="top">
|
||||
<IconButton
|
||||
@@ -1208,12 +1590,12 @@ const Clients1 = () => {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Date</TableCell>
|
||||
<TableCell>Slab</TableCell>
|
||||
<TableCell>Vehicle</TableCell>
|
||||
<TableCell>Base Price</TableCell>
|
||||
<TableCell>Min Kms</TableCell>
|
||||
<TableCell>Price/Km</TableCell>
|
||||
<TableCell>Other Charges</TableCell>
|
||||
<TableCell>Price/Kg</TableCell>
|
||||
<TableCell>Handling</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -1225,14 +1607,14 @@ const Clients1 = () => {
|
||||
</TableRow>
|
||||
) : (
|
||||
clientpricelist.map((val, i) => (
|
||||
<TableRow key={val.id || i} sx={{ bgcolor: 'white' }}>
|
||||
<TableRow key={val.pricingid || i} sx={{ bgcolor: 'white' }}>
|
||||
<TableCell>{i + 1}</TableCell>
|
||||
<TableCell>{dayjs(val.pricingdate).format('DD-MM-YYYY')}</TableCell>
|
||||
<TableCell>{val.slab}</TableCell>
|
||||
<TableCell>{val.baseprice}</TableCell>
|
||||
<TableCell>{val.minkm}</TableCell>
|
||||
<TableCell>{val.priceperkm}</TableCell>
|
||||
<TableCell>{val.othercharges}</TableCell>
|
||||
<TableCell>{val.vehicletype || '—'}</TableCell>
|
||||
<TableCell>{val.baseprice ?? '—'}</TableCell>
|
||||
<TableCell>{val.priceperkm ?? '—'}</TableCell>
|
||||
<TableCell>{val.priceperkg ?? '—'}</TableCell>
|
||||
<TableCell>{val.handlingcharges ?? '—'}</TableCell>
|
||||
<TableCell>{val.status || '—'}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
@@ -1667,12 +2049,12 @@ const Clients1 = () => {
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>#</TableCell>
|
||||
<TableCell>Date</TableCell>
|
||||
<TableCell>Slab</TableCell>
|
||||
<TableCell>Vehicle</TableCell>
|
||||
<TableCell>Base Price</TableCell>
|
||||
<TableCell>Min Kms</TableCell>
|
||||
<TableCell>Price/Km</TableCell>
|
||||
<TableCell>Other Charges</TableCell>
|
||||
<TableCell>Price/Kg</TableCell>
|
||||
<TableCell>Handling</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -1684,14 +2066,14 @@ const Clients1 = () => {
|
||||
</TableRow>
|
||||
) : (
|
||||
clientpricelist.map((val, i) => (
|
||||
<TableRow key={val.id || i} sx={{ bgcolor: 'white' }}>
|
||||
<TableRow key={val.pricingid || i} sx={{ bgcolor: 'white' }}>
|
||||
<TableCell>{i + 1}</TableCell>
|
||||
<TableCell>{dayjs(val.pricingdate).format('DD-MM-YYYY')}</TableCell>
|
||||
<TableCell>{val.slab}</TableCell>
|
||||
<TableCell>{val.baseprice}</TableCell>
|
||||
<TableCell>{val.minkm}</TableCell>
|
||||
<TableCell>{val.priceperkm}</TableCell>
|
||||
<TableCell>{val.othercharges}</TableCell>
|
||||
<TableCell>{val.vehicletype || '—'}</TableCell>
|
||||
<TableCell>{val.baseprice ?? '—'}</TableCell>
|
||||
<TableCell>{val.priceperkm ?? '—'}</TableCell>
|
||||
<TableCell>{val.priceperkg ?? '—'}</TableCell>
|
||||
<TableCell>{val.handlingcharges ?? '—'}</TableCell>
|
||||
<TableCell>{val.status || '—'}</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
@@ -1861,6 +2243,30 @@ const Clients1 = () => {
|
||||
>
|
||||
{expanded ? <EyeInvisibleOutlined style={{ fontSize: 14 }} /> : <EyeOutlined style={{ fontSize: 14 }} />}
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: soft('#0ea5e9'),
|
||||
color: '#0ea5e9',
|
||||
border: `1px solid ${edge('#0ea5e9')}`,
|
||||
'&:hover': { bgcolor: '#0ea5e9', color: '#fff' }
|
||||
}}
|
||||
onClick={() => setLocationsDialogTenant(row)}
|
||||
>
|
||||
<MdPlace size={14} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
sx={{
|
||||
bgcolor: soft('#14b8a6'),
|
||||
color: '#14b8a6',
|
||||
border: `1px solid ${edge('#14b8a6')}`,
|
||||
'&:hover': { bgcolor: '#14b8a6', color: '#fff' }
|
||||
}}
|
||||
onClick={() => setCustomersDialogTenant(row)}
|
||||
>
|
||||
<MdPeopleAlt size={14} />
|
||||
</IconButton>
|
||||
{value0 !== 1 && (
|
||||
<IconButton
|
||||
size="small"
|
||||
@@ -1955,24 +2361,24 @@ const Clients1 = () => {
|
||||
<Grid container spacing={2.5} alignItems={'center'}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormLabel>Select Slab</FormLabel>
|
||||
<Autocomplete
|
||||
disablePortal
|
||||
id="combo-box-demo"
|
||||
options={appPricing}
|
||||
getOptionLabel={(option) => `${option.slab}`}
|
||||
<Select
|
||||
fullWidth
|
||||
selectOnFocus
|
||||
renderInput={(params) => <TextField {...params} label="Slab" />}
|
||||
onChange={(event, value, reason) => {
|
||||
setSelectedPricing(value);
|
||||
console.log('pricing', value);
|
||||
displayEmpty
|
||||
value={selectedPricing.vehicletype || ''}
|
||||
onChange={(e) => {
|
||||
setSelectedPricing({ ...selectedPricing, vehicletype: e.target.value });
|
||||
setIsprice(false);
|
||||
|
||||
if (reason === 'clear') {
|
||||
setIsprice(true);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<MenuItem value="" disabled>
|
||||
Choose vehicle type
|
||||
</MenuItem>
|
||||
{['Bike', 'Scooter', 'Bicycle', 'Car', 'Van'].map((v) => (
|
||||
<MenuItem key={v} value={v}>
|
||||
{v}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
@@ -2005,43 +2411,57 @@ const Clients1 = () => {
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormLabel>Min Kms</FormLabel>
|
||||
<FormLabel>Base Weight (kg)</FormLabel>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
value={!isPrice ? selectedPricing.minkm : 0}
|
||||
value={!isPrice ? selectedPricing.baseweight : 0}
|
||||
onChange={(e) => {
|
||||
setSelectedPricing({
|
||||
...selectedPricing,
|
||||
minkm: e.target.value
|
||||
baseweight: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormLabel>Max Kms</FormLabel>
|
||||
<FormLabel>Price/Kg</FormLabel>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
value={!isPrice ? selectedPricing.maxkm : 0}
|
||||
value={!isPrice ? selectedPricing.priceperkg : 0}
|
||||
onChange={(e) => {
|
||||
setSelectedPricing({
|
||||
...selectedPricing,
|
||||
maxkm: e.target.value
|
||||
priceperkg: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormLabel>Min Orders</FormLabel>
|
||||
<FormLabel>Base Distance (km)</FormLabel>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
value={!isPrice ? selectedPricing.minorder : 0}
|
||||
value={!isPrice ? selectedPricing.basedistance : 0}
|
||||
onChange={(e) => {
|
||||
setSelectedPricing({
|
||||
...selectedPricing,
|
||||
minorder: e.target.value
|
||||
basedistance: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<FormLabel>Handling Charges</FormLabel>
|
||||
<TextField
|
||||
type="number"
|
||||
fullWidth
|
||||
value={!isPrice ? selectedPricing.handlingcharges : 0}
|
||||
onChange={(e) => {
|
||||
setSelectedPricing({
|
||||
...selectedPricing,
|
||||
handlingcharges: e.target.value
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -2093,6 +2513,18 @@ const Clients1 = () => {
|
||||
</Grid>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<TenantLocationsDialog
|
||||
tenant={locationsDialogTenant}
|
||||
open={!!locationsDialogTenant}
|
||||
onClose={() => setLocationsDialogTenant(null)}
|
||||
/>
|
||||
|
||||
<TenantCustomersDialog
|
||||
tenant={customersDialogTenant}
|
||||
open={!!customersDialogTenant}
|
||||
onClose={() => setCustomersDialogTenant(null)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,75 +1,43 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
// material-ui
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Avatar, Box, Button, FormLabel, Grid, InputLabel, MenuItem, Paper, Select, Stack, TextField, Typography, useMediaQuery } from '@mui/material';
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
FormControlLabel,
|
||||
Grid,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Paper,
|
||||
Select,
|
||||
Stack,
|
||||
Switch,
|
||||
TextField,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import { MdPersonAddAlt1 } from 'react-icons/md';
|
||||
|
||||
// third-party
|
||||
// import { PatternFormat } from 'react-number-format';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
// project import
|
||||
import MainCard from 'components/MainCard';
|
||||
import axios from 'axios';
|
||||
import AddressAutocomplete, { geocodeAddress } from 'components/nearle_components/AddressAutocomplete';
|
||||
import { createAdminTenant } from 'pages/api/doormileApi';
|
||||
import Loader from 'components/Loader';
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { DT, tint } from 'themes/dt/tokens';
|
||||
|
||||
// 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();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// fetchprofiledetails(localStorage.getItem('appuserid'));
|
||||
// fetchprofiledetails(181);
|
||||
if (localStorage.getItem('tenantid')) {
|
||||
fetchtenantinfo(localStorage.getItem('tenantid'));
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
geocodeAddress(address).then((place) => {
|
||||
if (active && place) {
|
||||
setLatlong({ lat: place.geometry.location.lat(), lng: place.geometry.location.lng() });
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [address]);
|
||||
const [tenantname, setTenantname] = useState('');
|
||||
const [primaryemail, setPrimaryemail] = useState('');
|
||||
const [primarycontact, setPrimarycontact] = useState('');
|
||||
const [status, setStatus] = useState('active');
|
||||
const [requiredeliveryotp, setRequiredeliveryotp] = useState(false);
|
||||
|
||||
const opentoast = (message) => {
|
||||
enqueueSnackbar(message, {
|
||||
@@ -77,192 +45,53 @@ const Createclient = () => {
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
// console.log(alertmessage)
|
||||
};
|
||||
|
||||
const fetchprofiledetails = async (userid) => {
|
||||
if (userid) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await axios
|
||||
.get(`${process.env.REACT_APP_URL2}/tenants/getclient?id=${userid}`)
|
||||
.then((res) => {
|
||||
console.log(res);
|
||||
if (res.data.message === 'Successful') {
|
||||
let res1 = res.data.details;
|
||||
setMobilenumber(res1.contactno);
|
||||
setEmailaddress(res1.primaryemail);
|
||||
setAddress(res1.address);
|
||||
setCity(res1.city);
|
||||
setZipcode(res1.postcode);
|
||||
setState(res1.state);
|
||||
setSuburb(res1.suburb);
|
||||
setLatlong({
|
||||
lat: res1.latitude,
|
||||
lng: res1.longitude
|
||||
});
|
||||
}
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
const createTenantMutation = useMutation({
|
||||
mutationFn: (payload) => createAdminTenant(payload),
|
||||
onSuccess: (res) => {
|
||||
if (res.success) {
|
||||
enqueueSnackbar('Client created successfully', {
|
||||
variant: 'success',
|
||||
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
||||
autoHideDuration: 2000
|
||||
});
|
||||
navigate('/doormile/tenants');
|
||||
} else {
|
||||
opentoast(res.message || 'Client already exists');
|
||||
}
|
||||
},
|
||||
onError: (err) => {
|
||||
opentoast(err.response?.data?.message || err.message || 'Failed to create client');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddressPlaceSelected = (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':
|
||||
case 'sublocality_level_1':
|
||||
suburb1 = place.address_components[i].long_name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
setCity(city1 || '');
|
||||
setState(state1 || '');
|
||||
setZipcode(zipcode1 || '');
|
||||
setSuburb(suburb1 || '');
|
||||
};
|
||||
|
||||
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');
|
||||
} else if (!mobilenumber) {
|
||||
opentoast('Fill Mobile Number');
|
||||
} else if (!emailaddress) {
|
||||
opentoast('Fill emailaddress');
|
||||
} else if (!address) {
|
||||
opentoast('Fill 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');
|
||||
const createprofile = () => {
|
||||
if (!tenantname) {
|
||||
opentoast('Fill Client / Business Name');
|
||||
} else if (!primarycontact || primarycontact.length !== 10) {
|
||||
opentoast('Fill a valid 10-digit Contact Number');
|
||||
} else if (!primaryemail) {
|
||||
opentoast('Fill Primary Email');
|
||||
} 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
|
||||
// POST /admin/tenants — requiredeliveryotp is a pointer server-side;
|
||||
// it's opt-in and off by default, so we only send it when the operator
|
||||
// explicitly turns it on rather than always sending false.
|
||||
const obj = {
|
||||
tenantname,
|
||||
primaryemail,
|
||||
primarycontact,
|
||||
status,
|
||||
...(requiredeliveryotp ? { requiredeliveryotp: true } : {})
|
||||
};
|
||||
|
||||
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');
|
||||
// setTimeout(()=>{
|
||||
// fetchprofiledetails(localStorage.getItem('appuserid'));
|
||||
|
||||
// },2000)
|
||||
} 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);
|
||||
}
|
||||
createTenantMutation.mutate(obj);
|
||||
}
|
||||
};
|
||||
|
||||
// const [experience, setExperience] = useState('0');
|
||||
|
||||
// const handleChange = (event) => {
|
||||
// setExperience(event.target.value);
|
||||
// };
|
||||
return (
|
||||
<>
|
||||
{loading && <Loader />}
|
||||
{createTenantMutation.isPending && <Loader />}
|
||||
|
||||
<Grid item xs={12} sx={{ mb: 2 }}>
|
||||
<Paper
|
||||
@@ -288,289 +117,73 @@ const Createclient = () => {
|
||||
sx={{ borderRadius: DT.radiusCard + 'px', boxShadow: DT.shadowSoft, borderColor: DT.borderSubtle }}
|
||||
>
|
||||
<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="tenant-name">Client / Business Name</InputLabel>
|
||||
<TextField
|
||||
fullWidth
|
||||
id="personal-last-name"
|
||||
placeholder="Name"
|
||||
onChange={(e) => setFirstname(e.target.value)}
|
||||
value={firstname}
|
||||
id="tenant-name"
|
||||
placeholder="e.g. DailyGrubs"
|
||||
onChange={(e) => setTenantname(e.target.value)}
|
||||
value={tenantname}
|
||||
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="tenant-status">Status</InputLabel>
|
||||
<Select id="tenant-status" fullWidth value={status} onChange={(e) => setStatus(e.target.value)}>
|
||||
<MenuItem value="active">Active</MenuItem>
|
||||
<MenuItem value="inactive">Inactive</MenuItem>
|
||||
</Select>
|
||||
</Stack>
|
||||
</Grid>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="tenant-contact">Primary Contact 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="tenant-contact"
|
||||
fullWidth
|
||||
// customInput={TextField}
|
||||
placeholder="Phone Number"
|
||||
// defaultValue="8654239581"
|
||||
// onBlur={() => { }}
|
||||
placeholder="Contact Number"
|
||||
onChange={(e) => {
|
||||
if (e.target.value.toString().length <= 10) {
|
||||
setMobilenumber(e.target.value);
|
||||
setPrimarycontact(e.target.value);
|
||||
}
|
||||
}}
|
||||
value={mobilenumber}
|
||||
value={primarycontact}
|
||||
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="tenant-email">Primary Email</InputLabel>
|
||||
<TextField
|
||||
type="email"
|
||||
fullWidth
|
||||
// defaultValue="stebin.ben@gmail.com"
|
||||
id="personal-email"
|
||||
placeholder="Email Address"
|
||||
onChange={(e) => setEmailaddress(e.target.value)}
|
||||
value={emailaddress}
|
||||
id="tenant-email"
|
||||
placeholder="Primary Email Address"
|
||||
onChange={(e) => setPrimaryemail(e.target.value)}
|
||||
value={primaryemail}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-address">Address</InputLabel>
|
||||
<AddressAutocomplete
|
||||
id="personal-address"
|
||||
fullWidth
|
||||
placeholder="Address"
|
||||
value={address}
|
||||
onChange={setAddress}
|
||||
onPlaceSelected={handleAddressPlaceSelected}
|
||||
/>
|
||||
</Stack>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Stack spacing={1.25}>
|
||||
<InputLabel htmlFor="personal-location">Suburb</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"
|
||||
placeholder="City"
|
||||
onChange={(e) => setCity(e.target.value)}
|
||||
value={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) => 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>
|
||||
<FormControlLabel
|
||||
control={<Switch checked={requiredeliveryotp} onChange={(e) => setRequiredeliveryotp(e.target.checked)} />}
|
||||
label="Require delivery OTP for this client (off by default)"
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</MainCard>
|
||||
@@ -582,7 +195,7 @@ const Createclient = () => {
|
||||
alignItems={{ xs: 'stretch', sm: 'center' }}
|
||||
spacing={2}
|
||||
>
|
||||
<Button variant="contained" onClick={() => createprofile()} fullWidth={isMobile}>
|
||||
<Button variant="contained" onClick={createprofile} fullWidth={isMobile}>
|
||||
Create
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user