updates on the ui changes and table

This commit is contained in:
2026-08-17 15:39:46 +05:30
parent a46212f291
commit 809d046323
38 changed files with 6403 additions and 7019 deletions

View File

@@ -9,7 +9,6 @@ import { HiOutlineArrowLeft } from 'react-icons/hi';
import { Grid } from '@astryxdesign/core/Grid';
import { HStack } from '@astryxdesign/core/HStack';
import { VStack } from '@astryxdesign/core/VStack';
import { Card } from '@astryxdesign/core/Card';
import { Text } from '@astryxdesign/core/Text';
import { Button } from '@astryxdesign/core/Button';
import { IconButton } from '@astryxdesign/core/IconButton';
@@ -22,6 +21,7 @@ import { Table, TableHeader, TableBody, TableRow, TableCell, TableHeaderCell } f
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import PageHeader from 'components/nearle_components/PageHeader';
import { PageShell, KpiGrid, DataCard } from 'components/nearle_components/PageLayout';
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
import { parseDoormileTimestamp } from 'utils/doormileTimestamp';
import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from '../../api/api';
@@ -210,7 +210,10 @@ const OrdersPreview = () => {
enabled: appId !== 0 // Ensures query runs only when appId is valid
});
const paymentOptions = useMemo(() => paymentModes.map((p) => ({ value: String(p.apptypeid), label: p.label || p.name || `Type #${p.apptypeid}` })), [paymentModes]);
const paymentOptions = useMemo(
() => paymentModes.map((p) => ({ value: String(p.apptypeid), label: p.label || p.name || `Type #${p.apptypeid}` })),
[paymentModes]
);
const riderOptions = useMemo(() => ridersList.map((r) => ({ value: String(r.userid), label: r.label })), [ridersList]);
// ======================================================= || notifyRiderMutation || =======================================================
@@ -368,104 +371,124 @@ const OrdersPreview = () => {
<>
{(paymentModesLoading || ridersListLoading || isLoading) && <Loader />}
<PageHeader
title="Assign Orders"
subtitle="Review before committing"
action={
<>
<IconButton label="Back to orders" tooltip="Back to orders" variant="secondary" icon={<HiOutlineArrowLeft size={20} />} onClick={() => navigate('/doormile/orders')} />
<Button label="Re-Assign" variant="secondary" icon={<MdArrowBack size={16} />} />
</>
}
/>
<PageShell>
<PageHeader
title="Assign Orders"
subtitle="Review before committing"
action={
<>
<IconButton
label="Back to orders"
tooltip="Back to orders"
variant="secondary"
icon={<HiOutlineArrowLeft size={20} />}
onClick={() => navigate('/doormile/orders')}
/>
<Button label="Re-Assign" variant="secondary" icon={<MdArrowBack size={16} />} />
</>
}
/>
{aiMode == 1 && (
<Grid columns={{ minWidth: 200, max: 4, repeat: 'fit' }} gap={3} style={{ marginBottom: 16 }}>
<HoverSocialCard secondary={metaData?.total_orders} primary="Orders" percentage={<MdDirectionsBike />} color={STATUS.delivered} />
<HoverSocialCard secondary={metaData?.total_riders} primary="Riders" percentage={<MdDirectionsBike />} color={STATUS.pending} />
<HoverSocialCard secondary={zoneData?.length} primary="Zones" percentage={<FaMapLocationDot />} color="#0ea5e9" />
<HoverSocialCard secondary={zoneData?.length} primary="Kilometer" percentage={<FaMapLocationDot />} color={STATUS.cancelled} />
</Grid>
)}
{aiMode == 1 && (
<KpiGrid>
<HoverSocialCard
secondary={metaData?.total_orders}
primary="Orders"
percentage={<MdDirectionsBike />}
color={STATUS.delivered}
/>
<HoverSocialCard secondary={metaData?.total_riders} primary="Riders" percentage={<MdDirectionsBike />} color={STATUS.pending} />
<HoverSocialCard secondary={zoneData?.length} primary="Zones" percentage={<FaMapLocationDot />} color="#0ea5e9" />
<HoverSocialCard secondary={zoneData?.length} primary="Kilometer" percentage={<FaMapLocationDot />} color={STATUS.cancelled} />
</KpiGrid>
)}
<Card padding={0} elevation="none" style={{ overflow: 'hidden' }}>
{isMobile ? (
<MobileOrdersList list={finaldeliveryList} aiMode={aiMode} />
) : (
<TableScroll minWidth={aiMode == 1 ? 1400 : 1200}>
<Table density="balanced" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableHeaderCell>#</TableHeaderCell>
{aiMode == 1 && <TableHeaderCell>Zone</TableHeaderCell>}
<TableHeaderCell>Tenant</TableHeaderCell>
<TableHeaderCell>Order Location</TableHeaderCell>
<TableHeaderCell>Pickup</TableHeaderCell>
<TableHeaderCell>Delivery</TableHeaderCell>
<TableHeaderCell>Notes</TableHeaderCell>
{aiMode == 1 && <TableHeaderCell>Rider</TableHeaderCell>}
<TableHeaderCell style={{ textAlign: 'center' }}>Type</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Profit</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>KMS</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{finaldeliveryList?.length == 0 && (
<DataCard>
{isMobile ? (
<MobileOrdersList list={finaldeliveryList} aiMode={aiMode} />
) : (
<TableScroll minWidth={aiMode == 1 ? 1400 : 1200}>
<Table density="balanced" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableCell colSpan={10}>
<EmptyState title="No orders to preview" icon={<MdDirectionsBike size={28} />} />
</TableCell>
<TableHeaderCell>#</TableHeaderCell>
{aiMode == 1 && <TableHeaderCell>Zone</TableHeaderCell>}
<TableHeaderCell>Tenant</TableHeaderCell>
<TableHeaderCell>Order Location</TableHeaderCell>
<TableHeaderCell>Pickup</TableHeaderCell>
<TableHeaderCell>Delivery</TableHeaderCell>
<TableHeaderCell>Notes</TableHeaderCell>
{aiMode == 1 && <TableHeaderCell>Rider</TableHeaderCell>}
<TableHeaderCell style={{ textAlign: 'center' }}>Type</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Profit</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>KMS</TableHeaderCell>
</TableRow>
)}
{finaldeliveryList?.map((val, index) => renderRow(val, index))}
</TableBody>
</Table>
</TableScroll>
)}
</TableHeader>
<TableBody>
{finaldeliveryList?.length == 0 && (
<TableRow>
<TableCell colSpan={10}>
<EmptyState title="No orders to preview" icon={<MdDirectionsBike size={28} />} />
</TableCell>
</TableRow>
)}
{finaldeliveryList?.map((val, index) => renderRow(val, index))}
</TableBody>
</Table>
</TableScroll>
)}
<Divider />
{aiMode == 0 && (
<Grid columns={{ minWidth: 240 }} gap={3} style={{ padding: 16 }}>
<Selector
label="Choose Payment"
options={paymentOptions}
value={payment ? String(payment.apptypeid) : ''}
onChange={(v) => {
if (!v) {
setPayment(null);
return;
}
const newValue = paymentModes.find((p) => String(p.apptypeid) === v);
setPayment(newValue);
setFinalDeliveryList((list) => list.map((item) => ({ ...item, paymenttype: newValue.apptypeid })));
}}
hasClear
hasSearch
<Divider />
{aiMode == 0 && (
<Grid columns={{ minWidth: 240, repeat: 'fit' }} gap={3} style={{ padding: 16 }}>
<Selector
label="Choose Payment"
options={paymentOptions}
value={payment ? String(payment.apptypeid) : ''}
onChange={(v) => {
if (!v) {
setPayment(null);
return;
}
const newValue = paymentModes.find((p) => String(p.apptypeid) === v);
setPayment(newValue);
setFinalDeliveryList((list) => list.map((item) => ({ ...item, paymenttype: newValue.apptypeid })));
}}
hasClear
hasSearch
/>
<Selector
label="Choose Rider"
options={riderOptions}
value={rider ? String(rider.userid) : ''}
onChange={(v) => {
if (!v) {
setRider(null);
return;
}
const newValue = ridersList.find((r) => String(r.userid) === v);
setRider(newValue);
setFinalDeliveryList((list) =>
list.map((item) => ({ ...item, userid: newValue.userid, userfcmtoken: newValue.userfcmtoken }))
);
}}
hasClear
hasSearch
/>
</Grid>
)}
<Divider />
<HStack justify="end" gap={2} padding={3}>
<Button label="Back" variant="secondary" icon={<MdArrowBack size={16} />} onClick={() => navigate('/doormile/orders')} />
<Button
label="Finalise"
variant="primary"
isDisabled={aiMode === 0 && (!rider || !payment)}
onClick={handleManualCreateDelivery}
/>
<Selector
label="Choose Rider"
options={riderOptions}
value={rider ? String(rider.userid) : ''}
onChange={(v) => {
if (!v) {
setRider(null);
return;
}
const newValue = ridersList.find((r) => String(r.userid) === v);
setRider(newValue);
setFinalDeliveryList((list) => list.map((item) => ({ ...item, userid: newValue.userid, userfcmtoken: newValue.userfcmtoken })));
}}
hasClear
hasSearch
/>
</Grid>
)}
<Divider />
<HStack justify="end" gap={2} padding={3}>
<Button label="Back" variant="secondary" icon={<MdArrowBack size={16} />} onClick={() => navigate('/doormile/orders')} />
<Button label="Finalise" variant="primary" isDisabled={aiMode === 0 && (!rider || !payment)} onClick={handleManualCreateDelivery} />
</HStack>
</Card>
</HStack>
</DataCard>
</PageShell>
</>
);
};

File diff suppressed because it is too large Load Diff

View File

@@ -33,6 +33,7 @@ import { FaLocationDot } from 'react-icons/fa6';
import Loader from 'components/Loader';
import PageHeader from 'components/nearle_components/PageHeader';
import { PageShell } from 'components/nearle_components/PageLayout';
import { TableScroll } from 'themes/dt/primitives';
import './OrdersRedesign.css';
@@ -752,405 +753,489 @@ const MultipleOrders = () => {
<>
{(loading || btnLoading) && <Loader />}
<PageHeader
title="Create Multiple Orders"
subtitle="Bulk-create deliveries from CSV/Excel or saved customers"
action={<Badge variant={dropCust.length ? 'blue' : 'neutral'} label={`${dropCust.length} drop${dropCust.length === 1 ? '' : 's'}`} />}
/>
<PageShell>
<PageHeader
title="Create Multiple Orders"
subtitle="Bulk-create deliveries from CSV/Excel or saved customers"
action={
<Badge variant={dropCust.length ? 'blue' : 'neutral'} label={`${dropCust.length} drop${dropCust.length === 1 ? '' : 's'}`} />
}
/>
<Grid columns={{ minWidth: 420 }} gap={3}>
{/* LEFT — input fields */}
<VStack gap={3} padding={0}>
<Card padding={3} elevation="none">
<Text weight="bold" style={{ marginBottom: 12 }}>
Schedule &amp; Pickup
</Text>
<VStack gap={2} padding={0}>
{locationsStatus === 'loading' ? (
<Text color="secondary">Loading business locations…</Text>
) : locationsStatus === 'no-tenant' && isStaffLogin ? (
// Staff login (express-console-api.md: token tenantid
// 0/null = "Doormile's own staff... sees everything, all
// tenants") — pick which tenant this bulk order is for.
<Selector
label="Pick a tenant"
options={staffTenantOptions}
value={selectedTenantId}
onChange={(v) => setSelectedTenantId(v || '')}
placeholder="Pick a tenant to create orders for"
hasSearch
isLoading={staffTenantListLoading}
/>
) : locationsStatus === 'no-tenant' ? (
<Text style={{ color: '#b91c1c' }}>This login has no tenant assigned — sign in as a tenant user</Text>
) : locationsStatus === 'error' ? (
<Text style={{ color: '#b91c1c', cursor: 'pointer' }} onClick={() => gettenantlocations(tenantid)}>
Could not load business locations — tap to retry
</Text>
) : locationsStatus === 'empty' ? (
<Text style={{ color: '#b45309' }}>No saved business locations for this tenant</Text>
) : tenantLocations?.length === 1 ? (
<TextInput label="Business Location" value={tenantLocations[0] ? locationDisplayName(tenantLocations[0]) : ''} isDisabled />
) : (
<Selector
label="Business Location"
options={locationOptions}
value={locationValue ? String(locationValue.tenantlocationid) : ''}
onChange={(v) => {
if (!v) {
setLocationid(0);
setLocationValue(null);
setPickCust(null);
return;
}
const value = tenantLocations.find((l) => String(l.tenantlocationid) === v);
setLocationid(value?.tenantlocationid || 0);
setLocationValue(value || null);
setPickCust(value || null);
}}
placeholder="Select Business Location"
hasSearch
hasClear
/>
)}
<Grid columns={{ minWidth: 420, repeat: 'fit' }} gap={3}>
{/* LEFT — input fields */}
<VStack gap={3} padding={0}>
<Card padding={3} elevation="none">
<Text weight="bold" style={{ marginBottom: 12 }}>
Schedule &amp; Pickup
</Text>
<VStack gap={2} padding={0}>
{locationsStatus === 'loading' ? (
<Text color="secondary">Loading business locations…</Text>
) : locationsStatus === 'no-tenant' && isStaffLogin ? (
// Staff login (express-console-api.md: token tenantid
// 0/null = "Doormile's own staff... sees everything, all
// tenants") — pick which tenant this bulk order is for.
<Selector
label="Pick a tenant"
options={staffTenantOptions}
value={selectedTenantId}
onChange={(v) => setSelectedTenantId(v || '')}
placeholder="Pick a tenant to create orders for"
hasSearch
isLoading={staffTenantListLoading}
/>
) : locationsStatus === 'no-tenant' ? (
<Text style={{ color: '#b91c1c' }}>This login has no tenant assigned — sign in as a tenant user</Text>
) : locationsStatus === 'error' ? (
<Text style={{ color: '#b91c1c', cursor: 'pointer' }} onClick={() => gettenantlocations(tenantid)}>
Could not load business locations — tap to retry
</Text>
) : locationsStatus === 'empty' ? (
<Text style={{ color: '#b45309' }}>No saved business locations for this tenant</Text>
) : tenantLocations?.length === 1 ? (
<TextInput
label="Business Location"
value={tenantLocations[0] ? locationDisplayName(tenantLocations[0]) : ''}
isDisabled
/>
) : (
<Selector
label="Business Location"
options={locationOptions}
value={locationValue ? String(locationValue.tenantlocationid) : ''}
onChange={(v) => {
if (!v) {
setLocationid(0);
setLocationValue(null);
setPickCust(null);
return;
}
const value = tenantLocations.find((l) => String(l.tenantlocationid) === v);
setLocationid(value?.tenantlocationid || 0);
setLocationValue(value || null);
setPickCust(value || null);
}}
placeholder="Select Business Location"
hasSearch
hasClear
/>
)}
<Grid columns={{ minWidth: 180 }} gap={2}>
<DateInput
label="Pickup Date"
size="sm"
value={startdate ? dayjs(startdate).format('YYYY-MM-DD') : undefined}
min={dayjs().format('YYYY-MM-DD')}
onChange={(v) => {
if (!v || !dayjs(v).isValid()) {
setStartdate(dayjs().format('MM-DD-YYYY'));
return;
}
const diffDays = dayjs().diff(dayjs(v), 'd');
if (diffDays <= 0) {
setStartdate(dayjs(v).format('MM-DD-YYYY'));
setSelectedtime('');
setPickupSlot(null);
} else {
opentoast('Choose an upcoming date', 'warning');
setStartdate(dayjs().format('MM-DD-YYYY'));
}
}}
<Grid columns={{ minWidth: 180, repeat: 'fit' }} gap={2}>
<DateInput
label="Pickup Date"
size="sm"
value={startdate ? dayjs(startdate).format('YYYY-MM-DD') : undefined}
min={dayjs().format('YYYY-MM-DD')}
onChange={(v) => {
if (!v || !dayjs(v).isValid()) {
setStartdate(dayjs().format('MM-DD-YYYY'));
return;
}
const diffDays = dayjs().diff(dayjs(v), 'd');
if (diffDays <= 0) {
setStartdate(dayjs(v).format('MM-DD-YYYY'));
setSelectedtime('');
setPickupSlot(null);
} else {
opentoast('Choose an upcoming date', 'warning');
setStartdate(dayjs().format('MM-DD-YYYY'));
}
}}
/>
<Selector
label="Pickup Slot"
options={slotOptions}
value={selectedtime ? dayjs(selectedtime, 'hh:mm A').format('HH:mm') : ''}
onChange={(v) => {
if (!v) {
setSelectedtime('');
setPickupSlot(null);
return;
}
if (!locationid) {
OpenToast('Please select a Business Location first.', 'warning', 3000);
return;
}
const formattedTime = dayjs(v, 'HH:mm').format('hh:mm A');
setSelectedtime(formattedTime);
const finalDateTime = dayjs(`${startdate} ${formattedTime}`, 'MM-DD-YYYY hh:mm A').format('YYYY-MM-DD hh:mm A');
setPickupSlot(finalDateTime);
}}
placeholder="Select Pickup Slot"
hasSearch
/>
</Grid>
{pickCust ? (
<HStack
gap={2}
vAlign="center"
padding={2}
style={{ border: '1px solid #eef2f6', borderInlineStart: '3px solid #0ea5e9', borderRadius: 10, background: '#fbfcff' }}
>
<div
style={{
width: 28,
height: 28,
borderRadius: 8,
background: 'linear-gradient(135deg,#0ea5e9,#0284c7)',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
fontSize: 12
}}
>
<FaLocationDot />
</div>
<VStack gap={0} padding={0} minWidth={0}>
<Text weight="bold" maxLines={1}>
{pickCust.locationname || '—'}
</Text>
<Text type="supporting" color="secondary" maxLines={1}>
{pickCust.address || '—'}
</Text>
</VStack>
</HStack>
) : (
<Text type="supporting" color="secondary">
Pickup auto-fills once a Business Location is selected.
</Text>
)}
</VStack>
</Card>
<Card padding={3} elevation="none">
<Text weight="bold" style={{ marginBottom: 4 }}>
Order Notes
</Text>
<Text type="supporting" color="secondary" style={{ marginBottom: 8 }}>
Applied to every order
</Text>
<TextInput
label="Special Dispatch Notes"
isLabelHidden
placeholder="Gate codes, call instructions, special cargo care…"
value={otherinstructions}
onChange={setOtherinstructions}
/>
</Card>
<Card padding={3} elevation="none">
<Text weight="bold" style={{ marginBottom: 2 }}>
Bulk Summary
</Text>
<Text type="supporting" color="secondary" style={{ marginBottom: 12 }}>
Live totals
</Text>
<Grid columns={{ minWidth: 140, repeat: 'fit' }} gap={2}>
<MetricTile
icon={FaRoute}
label="Distance"
value={totaldist ? `${totaldist} km` : '—'}
accent="#0ea5e9"
active={!!totaldist}
/>
<Selector
label="Pickup Slot"
options={slotOptions}
value={selectedtime ? dayjs(selectedtime, 'hh:mm A').format('HH:mm') : ''}
onChange={(v) => {
if (!v) {
setSelectedtime('');
setPickupSlot(null);
return;
}
if (!locationid) {
OpenToast('Please select a Business Location first.', 'warning', 3000);
return;
}
const formattedTime = dayjs(v, 'HH:mm').format('hh:mm A');
setSelectedtime(formattedTime);
const finalDateTime = dayjs(`${startdate} ${formattedTime}`, 'MM-DD-YYYY hh:mm A').format('YYYY-MM-DD hh:mm A');
setPickupSlot(finalDateTime);
}}
placeholder="Select Pickup Slot"
hasSearch
<MetricTile icon={FaBoxes} label="Quantity" value={totalQty || 0} accent="#10b981" active={!!totalQty} />
<MetricTile
icon={FaMoneyBillWave}
label="Cash Collect"
value={`₹${Number(totalCash).toFixed(2)}`}
accent="#f59e0b"
active={!!totalCash}
/>
<MetricTile icon={FaTruck} label="Deliveries" value={dropCust.length} accent="#8b5cf6" active={dropCust.length > 0} />
</Grid>
{pickCust ? (
<HStack gap={2} vAlign="center" padding={2} style={{ border: '1px solid #eef2f6', borderInlineStart: '3px solid #0ea5e9', borderRadius: 10, background: '#fbfcff' }}>
<div
style={{
width: 28,
height: 28,
borderRadius: 8,
background: 'linear-gradient(135deg,#0ea5e9,#0284c7)',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
flexShrink: 0,
fontSize: 12
}}
>
<FaLocationDot />
{dropCust.length > 0 && (
<div className="total-charge-badge" style={{ marginTop: 14, padding: '10px 14px' }}>
<div className="total-charge-left">
<FaReceipt className="total-charge-icon" />
<div className="total-charge-label">Total Charge</div>
</div>
<VStack gap={0} padding={0} minWidth={0}>
<Text weight="bold" maxLines={1}>
{pickCust.locationname || '—'}
</Text>
<Text type="supporting" color="secondary" maxLines={1}>
{pickCust.address || '—'}
</Text>
</VStack>
</HStack>
) : (
<Text type="supporting" color="secondary">
Pickup auto-fills once a Business Location is selected.
</Text>
<div className="total-charge-val" style={{ fontSize: 17 }}>
₹{Number(totalAmt).toFixed(2)}
</div>
</div>
)}
</VStack>
</Card>
<Card padding={3} elevation="none">
<Text weight="bold" style={{ marginBottom: 4 }}>
Order Notes
</Text>
<Text type="supporting" color="secondary" style={{ marginBottom: 8 }}>
Applied to every order
</Text>
<TextInput
label="Special Dispatch Notes"
isLabelHidden
placeholder="Gate codes, call instructions, special cargo care…"
value={otherinstructions}
onChange={setOtherinstructions}
/>
</Card>
<Card padding={3} elevation="none">
<Text weight="bold" style={{ marginBottom: 2 }}>
Bulk Summary
</Text>
<Text type="supporting" color="secondary" style={{ marginBottom: 12 }}>
Live totals
</Text>
<Grid columns={{ minWidth: 140 }} gap={2}>
<MetricTile icon={FaRoute} label="Distance" value={totaldist ? `${totaldist} km` : '—'} accent="#0ea5e9" active={!!totaldist} />
<MetricTile icon={FaBoxes} label="Quantity" value={totalQty || 0} accent="#10b981" active={!!totalQty} />
<MetricTile icon={FaMoneyBillWave} label="Cash Collect" value={`₹${Number(totalCash).toFixed(2)}`} accent="#f59e0b" active={!!totalCash} />
<MetricTile icon={FaTruck} label="Deliveries" value={dropCust.length} accent="#8b5cf6" active={dropCust.length > 0} />
</Grid>
{dropCust.length > 0 && (
<div className="total-charge-badge" style={{ marginTop: 14, padding: '10px 14px' }}>
<div className="total-charge-left">
<FaReceipt className="total-charge-icon" />
<div className="total-charge-label">Total Charge</div>
</div>
<div className="total-charge-val" style={{ fontSize: 17 }}>
₹{Number(totalAmt).toFixed(2)}
</div>
<div style={{ marginTop: 14 }}>
<Button
label={`Dispatch ${dropCust.length || ''} ${dropCust.length === 1 ? 'Order' : 'Orders'}`.trim()}
variant="primary"
icon={<FaPaperPlane size={12} />}
isDisabled={!canSubmit}
isLoading={btnLoading}
onClick={createorders}
style={{ width: '100%' }}
/>
</div>
</Card>
</VStack>
{/* RIGHT — file / drop preview */}
<Card padding={2} elevation="none" style={{ display: 'flex', flexDirection: 'column' }}>
<HStack justify="between" vAlign="center" gap={1} wrap="wrap" style={{ marginBottom: 8 }}>
<HStack gap={2} vAlign="center">
<Text weight="bold">{previewMode === 'drops' ? 'Drop List' : previewMode === 'preview' ? 'File Preview' : 'Preview'}</Text>
<Badge variant="purple" label={previewMode === 'drops' ? dropCust.length : previewMode === 'preview' ? users.length : 0} />
{previewMode === 'drops' && <Badge variant="purple" label={`₹${Number(totalAmt).toFixed(2)} total`} />}
{previewMode === 'preview' && <Badge variant="warning" label="Awaiting Continue" />}
</HStack>
{previewMode !== 'empty' && (
<HStack gap={1}>
<Button
label="Excel / CSV"
variant="secondary"
size="sm"
icon={<MdOutlineCloudUpload size={14} />}
onClick={() => pickHeaderSource(0)}
/>
<Button
label="Selection"
variant="secondary"
size="sm"
icon={<FaUsers size={12} />}
onClick={() => pickHeaderSource(1)}
/>
</HStack>
)}
</HStack>
{fileName && (
<Text type="supporting" color="secondary" style={{ marginBottom: 8 }}>
{fileName}
</Text>
)}
<div style={{ marginTop: 14 }}>
<Button
label={`Dispatch ${dropCust.length || ''} ${dropCust.length === 1 ? 'Order' : 'Orders'}`.trim()}
variant="primary"
icon={<FaPaperPlane size={12} />}
isDisabled={!canSubmit}
isLoading={btnLoading}
onClick={createorders}
style={{ width: '100%' }}
/>
</div>
</Card>
</VStack>
{/* RIGHT — file / drop preview */}
<Card padding={2} elevation="none" style={{ display: 'flex', flexDirection: 'column' }}>
<HStack justify="between" vAlign="center" gap={1} wrap="wrap" style={{ marginBottom: 8 }}>
<HStack gap={2} vAlign="center">
<Text weight="bold">{previewMode === 'drops' ? 'Drop List' : previewMode === 'preview' ? 'File Preview' : 'Preview'}</Text>
<Badge variant="purple" label={previewMode === 'drops' ? dropCust.length : previewMode === 'preview' ? users.length : 0} />
{previewMode === 'drops' && <Badge variant="purple" label={`₹${Number(totalAmt).toFixed(2)} total`} />}
{previewMode === 'preview' && <Badge variant="warning" label="Awaiting Continue" />}
</HStack>
{previewMode !== 'empty' && (
<HStack gap={1}>
<Button label="Excel / CSV" variant="secondary" size="sm" icon={<MdOutlineCloudUpload size={14} />} onClick={() => pickHeaderSource(0)} />
<Button label="Selection" variant="secondary" size="sm" icon={<FaUsers size={12} />} onClick={() => pickHeaderSource(1)} />
{previewMode === 'preview' && users.length >= 1 && (
<HStack
justify="between"
vAlign="center"
gap={2}
padding={2}
style={{
marginBottom: 8,
background: 'rgba(245,158,11,0.06)',
border: '1.5px solid rgba(245,158,11,0.25)',
borderRadius: 10
}}
>
<VStack gap={0} padding={0}>
<Text weight="bold" style={{ color: '#b45309' }}>
Process &amp; Calculate Distances
</Text>
<Text type="supporting" style={{ color: '#b45309' }}>
Click continue to import spreadsheet rows and calculate drop charges.
</Text>
</VStack>
<Button
label="Continue"
variant="primary"
size="sm"
onClick={() => users.forEach((customer) => handleCheckboxChange1(customer))}
/>
</HStack>
)}
</HStack>
{fileName && (
<Text type="supporting" color="secondary" style={{ marginBottom: 8 }}>
{fileName}
</Text>
)}
{previewMode === 'preview' && users.length >= 1 && (
<HStack justify="between" vAlign="center" gap={2} padding={2} style={{ marginBottom: 8, background: 'rgba(245,158,11,0.06)', border: '1.5px solid rgba(245,158,11,0.25)', borderRadius: 10 }}>
<VStack gap={0} padding={0}>
<Text weight="bold" style={{ color: '#b45309' }}>
Process &amp; Calculate Distances
</Text>
<Text type="supporting" style={{ color: '#b45309' }}>
Click continue to import spreadsheet rows and calculate drop charges.
</Text>
</VStack>
<Button
label="Continue"
variant="primary"
size="sm"
onClick={() => users.forEach((customer) => handleCheckboxChange1(customer))}
/>
</HStack>
)}
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
{previewMode === 'drops' && (
<TableScroll minWidth={720}>
<Table density="compact" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableHeaderCell>#</TableHeaderCell>
<TableHeaderCell>Customer</TableHeaderCell>
<TableHeaderCell>Address</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Qty</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Cash</TableHeaderCell>
<TableHeaderCell>Km</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'right' }}>Charge</TableHeaderCell>
<TableHeaderCell></TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{dropCust.map((customer, index) => (
<TableRow key={customer._rowkey || index}>
<TableCell>{index + 1}</TableCell>
<div style={{ flex: 1, minHeight: 0, overflow: 'auto' }}>
{previewMode === 'drops' && (
<TableScroll minWidth={720}>
<Table density="compact" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableHeaderCell>#</TableHeaderCell>
<TableHeaderCell>Customer</TableHeaderCell>
<TableHeaderCell>Address</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Qty</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Cash</TableHeaderCell>
<TableHeaderCell>Km</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'right' }}>Charge</TableHeaderCell>
<TableHeaderCell></TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{dropCust.map((customer, index) => (
<TableRow key={customer._rowkey || index}>
<TableCell>{index + 1}</TableCell>
<TableCell>
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
{customer.firstname}
</Text>
</TableCell>
<TableCell style={{ maxWidth: 260 }}>
{customer.latitude == null || customer.longitude == null ? (
<AddressAutocomplete
id={`drop-address-${customer._rowkey}`}
placeholder="Search address…"
value={customer.address || ''}
onChange={(text) =>
setDropCust((prev) => prev.map((c) => (c._rowkey === customer._rowkey ? { ...c, address: text } : c)))
}
onPlaceSelected={(place) => handleRowPlaceSelected(customer._rowkey, place)}
bias={pickCust?.latitude ? { lat: pickCust.latitude, lng: pickCust.longitude } : undefined}
/>
) : (
<Tooltip content={customer.address || ''}>
<Text maxLines={1} style={{ maxWidth: 260 }}>
{customer.address}
</Text>
</Tooltip>
)}
</TableCell>
<TableCell style={{ textAlign: 'center' }}>
{uploadType === 0 ? (
customer.quantity
) : (
<NumberInput
label="Quantity"
isLabelHidden
value={customer.quantity || undefined}
onChange={(v) => handleQuantityChange(customer._rowkey, v ?? 0)}
size="sm"
/>
)}
</TableCell>
<TableCell style={{ textAlign: 'center' }}>
{uploadType === 0 ? (
`₹${Number(customer.collectionamt || 0).toFixed(2)}`
) : (
<NumberInput
label="Collection amount"
isLabelHidden
value={customer.collectionamt || undefined}
onChange={(v) => handleCollectionAmtChange(customer._rowkey, v && v > 0 ? v : 0)}
size="sm"
/>
)}
</TableCell>
<TableCell>{customer.distance || '—'}</TableCell>
<TableCell style={{ textAlign: 'right' }}>
<Text weight="bold">₹{Number(customer?.totalcharge || 0).toFixed(2)}</Text>
</TableCell>
<TableCell>
<IconButton
label="Remove"
tooltip="Remove"
size="sm"
variant="destructive"
icon={<MdDeleteOutline size={14} />}
onClick={() => handleCheckboxChange1(customer)}
/>
</TableCell>
</TableRow>
))}
<TableRow>
<TableCell>
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
{customer.firstname}
<Text weight="bold">Total</Text>
</TableCell>
<TableCell colSpan={2} />
<TableCell style={{ textAlign: 'center' }}>
<Text weight="bold">{totalQty}</Text>
</TableCell>
<TableCell style={{ textAlign: 'center' }}>
<Text weight="bold">₹{Number(totalCash).toFixed(2)}</Text>
</TableCell>
<TableCell>
<Text weight="bold">{totaldist}</Text>
</TableCell>
<TableCell style={{ textAlign: 'right' }}>
<Text weight="bold" style={{ color: '#8b5cf6' }}>
₹{Number(totalAmt).toFixed(2)}
</Text>
</TableCell>
<TableCell style={{ maxWidth: 260 }}>
{customer.latitude == null || customer.longitude == null ? (
<AddressAutocomplete
id={`drop-address-${customer._rowkey}`}
placeholder="Search address…"
value={customer.address || ''}
onChange={(text) => setDropCust((prev) => prev.map((c) => (c._rowkey === customer._rowkey ? { ...c, address: text } : c)))}
onPlaceSelected={(place) => handleRowPlaceSelected(customer._rowkey, place)}
bias={pickCust?.latitude ? { lat: pickCust.latitude, lng: pickCust.longitude } : undefined}
/>
) : (
<Tooltip content={customer.address || ''}>
<Text maxLines={1} style={{ maxWidth: 260 }}>
{customer.address}
<TableCell />
</TableRow>
</TableBody>
</Table>
</TableScroll>
)}
{previewMode === 'preview' && (
<TableScroll minWidth={640}>
<Table density="compact" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableHeaderCell>#</TableHeaderCell>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>Contact</TableHeaderCell>
<TableHeaderCell>Address</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Qty</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Cash</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{users.map((u, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
{u.firstname || '—'}
</Text>
</TableCell>
<TableCell>{u.contactno || '—'}</TableCell>
<TableCell style={{ maxWidth: 240 }}>
<Tooltip content={u.address || ''}>
<Text maxLines={1} style={{ maxWidth: 240 }}>
{u.address || '—'}
</Text>
</Tooltip>
)}
</TableCell>
<TableCell style={{ textAlign: 'center' }}>
{uploadType === 0 ? (
customer.quantity
) : (
<NumberInput
label="Quantity"
isLabelHidden
value={customer.quantity || undefined}
onChange={(v) => handleQuantityChange(customer._rowkey, v ?? 0)}
size="sm"
/>
)}
</TableCell>
<TableCell style={{ textAlign: 'center' }}>
{uploadType === 0 ? (
`₹${Number(customer.collectionamt || 0).toFixed(2)}`
) : (
<NumberInput
label="Collection amount"
isLabelHidden
value={customer.collectionamt || undefined}
onChange={(v) => handleCollectionAmtChange(customer._rowkey, v && v > 0 ? v : 0)}
size="sm"
/>
)}
</TableCell>
<TableCell>{customer.distance || '—'}</TableCell>
<TableCell style={{ textAlign: 'right' }}>
<Text weight="bold">₹{Number(customer?.totalcharge || 0).toFixed(2)}</Text>
</TableCell>
<TableCell>
<IconButton label="Remove" tooltip="Remove" size="sm" variant="destructive" icon={<MdDeleteOutline size={14} />} onClick={() => handleCheckboxChange1(customer)} />
</TableCell>
</TableRow>
))}
<TableRow>
<TableCell>
<Text weight="bold">Total</Text>
</TableCell>
<TableCell colSpan={2} />
<TableCell style={{ textAlign: 'center' }}>
<Text weight="bold">{totalQty}</Text>
</TableCell>
<TableCell style={{ textAlign: 'center' }}>
<Text weight="bold">₹{Number(totalCash).toFixed(2)}</Text>
</TableCell>
<TableCell>
<Text weight="bold">{totaldist}</Text>
</TableCell>
<TableCell style={{ textAlign: 'right' }}>
<Text weight="bold" style={{ color: '#8b5cf6' }}>
₹{Number(totalAmt).toFixed(2)}
</Text>
</TableCell>
<TableCell />
</TableRow>
</TableBody>
</Table>
</TableScroll>
)}
</TableCell>
<TableCell style={{ textAlign: 'center' }}>{u.quantity ?? '—'}</TableCell>
<TableCell style={{ textAlign: 'center' }}>
{u.collectionamt != null ? `₹${Number(u.collectionamt).toFixed(2)}` : '—'}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableScroll>
)}
{previewMode === 'preview' && (
<TableScroll minWidth={640}>
<Table density="compact" dividers="rows" hasHover>
<TableHeader>
<TableRow>
<TableHeaderCell>#</TableHeaderCell>
<TableHeaderCell>Name</TableHeaderCell>
<TableHeaderCell>Contact</TableHeaderCell>
<TableHeaderCell>Address</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Qty</TableHeaderCell>
<TableHeaderCell style={{ textAlign: 'center' }}>Cash</TableHeaderCell>
</TableRow>
</TableHeader>
<TableBody>
{users.map((u, i) => (
<TableRow key={i}>
<TableCell>{i + 1}</TableCell>
<TableCell>
<Text weight="semibold" style={{ whiteSpace: 'nowrap' }}>
{u.firstname || '—'}
</Text>
</TableCell>
<TableCell>{u.contactno || '—'}</TableCell>
<TableCell style={{ maxWidth: 240 }}>
<Tooltip content={u.address || ''}>
<Text maxLines={1} style={{ maxWidth: 240 }}>
{u.address || '—'}
</Text>
</Tooltip>
</TableCell>
<TableCell style={{ textAlign: 'center' }}>{u.quantity ?? '—'}</TableCell>
<TableCell style={{ textAlign: 'center' }}>{u.collectionamt != null ? `₹${Number(u.collectionamt).toFixed(2)}` : '—'}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableScroll>
)}
{previewMode === 'empty' && (
<VStack vAlign="center" gap={2} padding={4} style={{ minHeight: 240, background: 'rgba(24,144,255,0.03)', border: '1px dashed rgba(24,144,255,0.25)', borderRadius: 12, textAlign: 'center' }}>
<Text weight="bold">Choose a Drop Source to begin</Text>
<Text type="supporting" color="secondary" style={{ maxWidth: 360 }}>
Select a source below to import or pick your delivery customers.
</Text>
<HStack gap={2} wrap="wrap" style={{ width: '100%', maxWidth: 480, marginTop: 8 }}>
<SourceTile icon={MdOutlineCloudUpload} title="Excel / CSV" sub="Bulk upload a sheet" accent="#0ea5e9" disabled={!prereqOk} onClick={() => pickEmptySource(0)} />
<SourceTile icon={FaUsers} title="Selection" sub="Pick saved customers" accent="#8b5cf6" disabled={!prereqOk} onClick={() => pickEmptySource(1)} />
</HStack>
</VStack>
)}
</div>
</Card>
</Grid>
{previewMode === 'empty' && (
<VStack
vAlign="center"
gap={2}
padding={4}
style={{
minHeight: 240,
background: 'rgba(24,144,255,0.03)',
border: '1px dashed rgba(24,144,255,0.25)',
borderRadius: 12,
textAlign: 'center'
}}
>
<Text weight="bold">Choose a Drop Source to begin</Text>
<Text type="supporting" color="secondary" style={{ maxWidth: 360 }}>
Select a source below to import or pick your delivery customers.
</Text>
<HStack gap={2} wrap="wrap" style={{ width: '100%', maxWidth: 480, marginTop: 8 }}>
<SourceTile
icon={MdOutlineCloudUpload}
title="Excel / CSV"
sub="Bulk upload a sheet"
accent="#0ea5e9"
disabled={!prereqOk}
onClick={() => pickEmptySource(0)}
/>
<SourceTile
icon={FaUsers}
title="Selection"
sub="Pick saved customers"
accent="#8b5cf6"
disabled={!prereqOk}
onClick={() => pickEmptySource(1)}
/>
</HStack>
</VStack>
)}
</div>
</Card>
</Grid>
</PageShell>
<input
accept=".csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
@@ -1188,7 +1273,13 @@ const MultipleOrders = () => {
const rowkey = `sel-${customer.appcustomerid}`;
const checked = dropCust.some((c) => c._rowkey === rowkey);
return (
<div key={customer.appcustomerid || index} style={{ padding: '8px 4px', borderBottom: index < filteredCustomerList.length - 1 ? '1px solid var(--color-border-subtle)' : 'none' }}>
<div
key={customer.appcustomerid || index}
style={{
padding: '8px 4px',
borderBottom: index < filteredCustomerList.length - 1 ? '1px solid var(--color-border-subtle)' : 'none'
}}
>
<CheckboxInput
label={`${customer.name} (${customer.phone})`}
description="Address not on file — add one after selecting"
@@ -1206,7 +1297,11 @@ const MultipleOrders = () => {
footer={
<LayoutFooter hasDivider>
<HStack justify="end">
<Button label={dropCust.length === 0 ? 'Close' : 'Continue'} variant={dropCust.length === 0 ? 'destructive' : 'primary'} onClick={() => setIsCustomerOpen(false)} />
<Button
label={dropCust.length === 0 ? 'Close' : 'Continue'}
variant={dropCust.length === 0 ? 'destructive' : 'primary'}
onClick={() => setIsCustomerOpen(false)}
/>
</HStack>
</LayoutFooter>
}

File diff suppressed because it is too large Load Diff