updates on the fix
This commit is contained in:
@@ -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't store an address against a customer yet — these fields aren't required and won'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 }}>
|
||||
|
||||
Reference in New Issue
Block a user