updates on the fix

This commit is contained in:
2026-08-12 12:55:18 +05:30
parent bf28249528
commit fabb74c326
15 changed files with 1478 additions and 528 deletions

View File

@@ -9,7 +9,6 @@ import {
Button,
Grid,
IconButton,
InputLabel,
Paper,
Stack,
Table,
@@ -24,6 +23,7 @@ import {
useMediaQuery,
useTheme
} from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import {
MdPeopleAlt,
MdOutlinePeopleAlt,
@@ -43,6 +43,7 @@ import Loader from 'components/Loader';
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
import PageHeader from 'components/nearle_components/PageHeader';
import StatCard from 'components/nearle_components/StatCard';
import AddressAutocomplete from 'components/nearle_components/AddressAutocomplete';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton';
import { getAdminCustomers, updateAdminCustomer } from 'pages/api/doormileApi';
@@ -54,6 +55,13 @@ import { enqueueSnackbar } from 'notistack';
// all. PATCH /admin/customers/:id is the only mutation the API exposes;
// there is no create/delete here by design ("Tenant-scoped through their
// bookings" — a customer only exists once they've ordered through a tenant).
//
// The edit dialog replicates nearle_console_express's customer edit dialog
// field-for-field (Name, Contact, Address, Location, City, State, Postcode,
// Landmark, Latitude, Longitude) — same layout, same AddressAutocomplete
// wiring — but only `name/phone/email` are ever sent in the PATCH body; the
// address fields have nowhere to persist to on this API (same "collected but
// not persisted" pattern as `pages/nearle/clients/createCustomer.js`).
// ============================================================================
const DT = {
@@ -97,6 +105,9 @@ const Customers = () => {
const [debouncedSearch, setDebouncedSearch] = useState('');
const [editRow, setEditRow] = useState(null);
const [form, setForm] = useState({});
const [addressInput, setAddressInput] = useState('');
const [pickAddress, setPickAddress] = useState({});
const [latLng, setLatLng] = useState({ latitude: '', longitude: '' });
const { data: customers = [], isLoading } = useQuery({ queryKey: ['admin-customers'], queryFn: getAdminCustomers });
@@ -139,6 +150,44 @@ const Customers = () => {
phone: row.phone || '',
email: row.email || ''
});
setAddressInput(row.address || '');
setPickAddress({
doorno: row.doorno || '',
suburb: row.suburb || '',
city: row.city || '',
state: row.state || '',
postcode: row.postcode || '',
landmark: row.landmark || ''
});
setLatLng({ latitude: row.latitude || '', longitude: row.longitude || '' });
};
// Same address_components parsing as createCustomer.js's handlePlaceSelected —
// AddressAutocomplete shapes Nominatim results to look like a Google Places `place`.
const handleAddressPlaceSelected = (place) => {
setLatLng({ latitude: place.geometry.location.lat(), longitude: place.geometry.location.lng() });
const parsed = { suburb: '', city: '', state: '', postcode: '' };
place.address_components.forEach((component) => {
component.types.forEach((type) => {
switch (type) {
case 'sublocality_level_1':
case 'sublocality':
parsed.suburb = component.long_name;
break;
case 'locality':
parsed.city = component.long_name;
break;
case 'administrative_area_level_1':
parsed.state = component.long_name;
break;
case 'postal_code':
parsed.postcode = component.long_name;
break;
}
});
});
setPickAddress((p) => ({ ...p, ...parsed }));
setAddressInput(place.formatted_address);
};
return (
@@ -188,13 +237,21 @@ const Customers = () => {
background: '#fff'
}}
>
<Stack direction={{ xs: 'column', sm: 'row' }} alignItems={{ xs: 'stretch', sm: 'center' }} justifyContent="space-between" spacing={1.25}>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'stretch', sm: 'center' }}
justifyContent="space-between"
spacing={1.25}
>
<Stack direction="row" alignItems="center" spacing={1.25}>
<AccentAvatar color={BRAND} size={32}>
<MdPeopleAlt size={18} />
</AccentAvatar>
<Stack>
<Typography variant="caption" sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Directory
</Typography>
<Typography variant="body2" sx={{ color: DT.textPrimary, fontWeight: 700 }}>
@@ -388,7 +445,14 @@ const Customers = () => {
)}
</Paper>
<Dialog open={!!editRow} onClose={() => setEditRow(null)} maxWidth="xs" fullWidth PaperProps={{ sx: { borderRadius: 3 } }}>
<Dialog
open={!!editRow}
onClose={() => setEditRow(null)}
maxWidth="lg"
fullWidth
fullScreen={isMobile}
PaperProps={{ sx: { borderRadius: { xs: 0, sm: 3 } } }}
>
<DialogTitle
sx={{
background: `linear-gradient(135deg, ${BRAND} 0%, ${BRAND_LIGHT} 100%)`,
@@ -401,7 +465,9 @@ const Customers = () => {
<FaRegEdit style={{ fontSize: 18 }} />
</Avatar>
<Stack>
<Typography sx={{ fontSize: 11, fontWeight: 700, opacity: 0.85, letterSpacing: 0.6, textTransform: 'uppercase', lineHeight: 1 }}>
<Typography
sx={{ fontSize: 11, fontWeight: 700, opacity: 0.85, letterSpacing: 0.6, textTransform: 'uppercase', lineHeight: 1 }}
>
Customer
</Typography>
<Typography sx={{ fontWeight: 800, fontSize: '1.1rem', lineHeight: 1.2, mt: 0.25 }}>
@@ -411,16 +477,22 @@ const Customers = () => {
</Stack>
</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ mt: 2 }}>
<Stack spacing={1}>
<InputLabel>Name</InputLabel>
<TextField fullWidth value={form.name || ''} onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))} />
</Stack>
<Stack spacing={1}>
<InputLabel>Phone Number</InputLabel>
<Grid container spacing={2} sx={{ mt: 2 }}>
<Grid item xs={12} sm={6}>
<Typography sx={{ mb: 1 }}>Customer Name</Typography>
<TextField
variant="outlined"
fullWidth
value={form.name || ''}
onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Typography sx={{ mb: 1 }}>Contact Number</Typography>
<Stack direction="row" spacing={1}>
<TextField value="+91" disabled sx={{ width: 64, cursor: 'not-allowed' }} />
<TextField variant="outlined" value="+91" disabled sx={{ width: 64, cursor: 'not-allowed' }} />
<TextField
variant="outlined"
fullWidth
value={form.phone || ''}
inputProps={{ maxLength: 10, inputMode: 'numeric', pattern: '[0-9]*' }}
@@ -428,18 +500,107 @@ const Customers = () => {
InputProps={{ startAdornment: <MdOutlinePhoneAndroid size={14} style={{ marginRight: 6, color: DT.textMuted }} /> }}
/>
</Stack>
</Stack>
<Stack spacing={1}>
<InputLabel>Email</InputLabel>
</Grid>
<Grid item xs={12}>
<Typography sx={{ mb: 1 }}>Email</Typography>
<TextField
variant="outlined"
fullWidth
type="email"
value={form.email || ''}
onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
InputProps={{ startAdornment: <MdMail size={14} style={{ marginRight: 6, color: DT.textMuted }} /> }}
/>
</Stack>
</Stack>
</Grid>
<Grid item xs={12}>
<Typography variant="caption" sx={{ color: DT.textMuted, fontStyle: 'italic' }}>
The backend doesn&apos;t store an address against a customer yet — these fields aren&apos;t required and won&apos;t be saved
on submit.
</Typography>
</Grid>
<Grid item xs={12}>
<Typography sx={{ mb: 1 }}>Address</Typography>
<AddressAutocomplete
id="edit-customer-address"
fullWidth
value={addressInput}
onChange={(text) => setAddressInput(text)}
onPlaceSelected={handleAddressPlaceSelected}
TextFieldProps={{
variant: 'outlined',
InputProps: {
endAdornment: (
<IconButton
onClick={() => {
setAddressInput('');
setPickAddress((p) => ({ ...p, suburb: '', city: '', state: '', postcode: '' }));
setLatLng({ latitude: '', longitude: '' });
}}
size="small"
>
<CloseIcon fontSize="small" />
</IconButton>
)
}
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Typography sx={{ mb: 1 }}>Location</Typography>
<TextField
variant="outlined"
fullWidth
value={pickAddress.suburb || ''}
onChange={(e) => setPickAddress((p) => ({ ...p, suburb: e.target.value }))}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Typography sx={{ mb: 1 }}>City</Typography>
<TextField
variant="outlined"
fullWidth
value={pickAddress.city || ''}
onChange={(e) => setPickAddress((p) => ({ ...p, city: e.target.value }))}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Typography sx={{ mb: 1 }}>State</Typography>
<TextField
variant="outlined"
fullWidth
value={pickAddress.state || ''}
onChange={(e) => setPickAddress((p) => ({ ...p, state: e.target.value }))}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Typography sx={{ mb: 1 }}>Postcode</Typography>
<TextField
variant="outlined"
fullWidth
value={pickAddress.postcode || ''}
onChange={(e) => setPickAddress((p) => ({ ...p, postcode: e.target.value }))}
/>
</Grid>
<Grid item xs={12}>
<Typography sx={{ mb: 1 }}>Landmark</Typography>
<TextField
variant="outlined"
fullWidth
value={pickAddress.landmark || ''}
onChange={(e) => setPickAddress((p) => ({ ...p, landmark: e.target.value }))}
/>
</Grid>
<Grid item xs={12} sm={6} sx={{ cursor: 'not-allowed' }}>
<Typography sx={{ mb: 1 }}>Latitude</Typography>
<TextField variant="outlined" fullWidth disabled value={latLng.latitude || ''} sx={{ cursor: 'not-allowed' }} />
</Grid>
<Grid item xs={12} sm={6} sx={{ cursor: 'not-allowed' }}>
<Typography sx={{ mb: 1 }}>Longitude</Typography>
<TextField variant="outlined" fullWidth disabled value={latLng.longitude || ''} sx={{ cursor: 'not-allowed' }} />
</Grid>
</Grid>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button onClick={() => setEditRow(null)} sx={{ color: DT.textSecondary }}>