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)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user