updates on the ai bot and the queries and the delivereis page

This commit is contained in:
2026-08-27 13:11:05 +05:30
parent c6607ae75d
commit e34e41ca96
8 changed files with 217 additions and 144 deletions

View File

@@ -87,6 +87,7 @@ const BOOKING_STATUS_TO_DELIVERY_STATUS = {
arrived: 'arrived', arrived: 'arrived',
converted_to_consignment: 'picked', converted_to_consignment: 'picked',
collected_by_miler: 'picked', collected_by_miler: 'picked',
collected: 'picked',
picked: 'picked', picked: 'picked',
out_for_delivery: 'active', out_for_delivery: 'active',
in_transit: 'active', in_transit: 'active',
@@ -115,7 +116,7 @@ const BOOKING_STATUS_TO_DELIVERY_STATUS = {
export const deriveDeliveryStatus = (bookingOrStatus, consignmentMapOrReachedAt) => { export const deriveDeliveryStatus = (bookingOrStatus, consignmentMapOrReachedAt) => {
if (!bookingOrStatus) return 'pending'; if (!bookingOrStatus) return 'pending';
// If passed a raw status string // If passed a simple status string
if (typeof bookingOrStatus === 'string') { if (typeof bookingOrStatus === 'string') {
const raw = bookingOrStatus.trim().toLowerCase(); const raw = bookingOrStatus.trim().toLowerCase();
if (raw === 'pickup_scheduled') { if (raw === 'pickup_scheduled') {
@@ -134,27 +135,29 @@ export const deriveDeliveryStatus = (bookingOrStatus, consignmentMapOrReachedAt)
const cStatus = consignmentStatusFor(b, typeof consignmentMapOrReachedAt === 'object' && consignmentMapOrReachedAt instanceof Map ? consignmentMapOrReachedAt : undefined); const cStatus = consignmentStatusFor(b, typeof consignmentMapOrReachedAt === 'object' && consignmentMapOrReachedAt instanceof Map ? consignmentMapOrReachedAt : undefined);
const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time; const reached = b.reachedat ?? b.reached_at ?? b.reachedAt ?? b.reachedtime ?? b.reached_time;
// If booking is Converted_To_Consignment, switch directly to consignmentstatus: // 1. If consignment status is present, it defines the live state after pickup:
// Converted_To_Consignment is a handoff marker, not a display state. // - 'collected_by_miler' / 'collected' / 'picked' -> 'picked' (Picked tab)
if (rawBookingStatus === 'converted_to_consignment') { // - 'out_for_delivery' / 'in_transit' / 'active' -> 'active' (Active tab, rider started delivery)
if (cStatus) { // - 'delivered' -> 'delivered' (Delivered tab)
return mapBookingStatusToDeliveryStatus(cStatus); // - 'cancelled' -> 'cancelled' (Cancelled tab)
} if (cStatus) {
const rawC = String(cStatus).trim().toLowerCase();
const mappedC = BOOKING_STATUS_TO_DELIVERY_STATUS[rawC];
if (mappedC) return mappedC;
}
// 2. Booking level handoff states:
if (rawBookingStatus === 'converted_to_consignment' || rawBookingStatus === 'collected_by_miler' || rawBookingStatus === 'picked') {
return 'picked'; return 'picked';
} }
// If consignment status is present, it wins: // 3. Before pickup (booking lifecycle):
if (cStatus) {
return mapBookingStatusToDeliveryStatus(cStatus);
}
// Before pickup (booking lifecycle):
if (rawBookingStatus === 'pickup_scheduled') { if (rawBookingStatus === 'pickup_scheduled') {
const isReached = reached != null && reached !== false && reached !== ''; const isReached = reached != null && reached !== false && reached !== '';
return isReached ? 'arrived' : 'accepted'; return isReached ? 'arrived' : 'accepted';
} }
return mapBookingStatusToDeliveryStatus(rawBookingStatus, reached); return BOOKING_STATUS_TO_DELIVERY_STATUS[rawBookingStatus] || rawBookingStatus;
}; };
export const mapBookingStatusToDeliveryStatus = (statusOrRow, maybeReachedAt) => { export const mapBookingStatusToDeliveryStatus = (statusOrRow, maybeReachedAt) => {
@@ -852,8 +855,8 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
// Pickup_Scheduled + reachedat present -> Arrived // Pickup_Scheduled + reachedat present -> Arrived
// Pickup_Scheduled + reachedat absent -> Accepted // Pickup_Scheduled + reachedat absent -> Accepted
orderstatus: effectiveDeliveryStatus, orderstatus: effectiveDeliveryStatus,
consignmentstatus: cStatus, consignmentstatus: cStatus || effectiveDeliveryStatus,
statusfromconsignment: cStatus != null || String(b.status || '').toLowerCase() === 'converted_to_consignment', statusfromconsignment: Boolean(cStatus && !['accepted', 'pending', 'arrived'].includes(effectiveDeliveryStatus)),
droplat: b.deliverylatitude, droplat: b.deliverylatitude,
droplon: b.deliverylongitude droplon: b.deliverylongitude
}; };

View File

@@ -953,40 +953,50 @@ body.dai-docked .astryx-layout-content {
} }
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
Write-action confirm card Write-action confirm card & buttons
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */
.dai-root .dai-action { .dai-root .dai-action {
padding: 10px 11px; padding: 12px 14px;
border: 1px solid var(--dai-border-strong); border: 1px solid #e2e8f0;
border-radius: 5px; border-radius: 12px;
background: var(--dai-surface-alt); background: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: column;
gap: 10px;
} }
.dai-root .dai-action-icon { .dai-root .dai-action-icon {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 0 0 auto; flex: 0 0 auto;
width: 26px; width: 28px;
height: 26px; height: 28px;
border-radius: 5px; border-radius: 8px;
color: #4f46e5; color: #4f46e5;
background: rgba(99, 102, 241, 0.1); background: rgba(99, 102, 241, 0.1);
} }
.dai-root .dai-action-summary { .dai-root .dai-action-summary {
font-size: 13px; font-size: 13px;
line-height: 1.35; line-height: 1.4;
font-weight: 550; font-weight: 600;
color: var(--dai-text); color: var(--dai-text);
} }
.dai-root .dai-action-note { .dai-root .dai-action-note {
font-size: 12px; font-size: 12px;
color: var(--dai-text-secondary); color: #64748b;
line-height: 1.4;
} }
.dai-root .dai-ok { .dai-root .dai-ok {
color: #047857; color: #047857;
font-weight: 500;
} }
.dai-root .dai-err { .dai-root .dai-err {
color: #b91c1c; color: #b91c1c;
font-weight: 500;
}
.dai-root .dai-action button {
cursor: pointer;
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
Overflow containment Overflow containment

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { VStack } from '@astryxdesign/core/VStack'; import { VStack } from '@astryxdesign/core/VStack';
import { HStack } from '@astryxdesign/core/HStack'; import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text'; import { Text } from '@astryxdesign/core/Text';
import { Button } from '@astryxdesign/core/Button'; import { Button } from '@/components/ui/button';
import { Selector } from '@astryxdesign/core/Selector'; import { Selector } from '@astryxdesign/core/Selector';
// ==============================|| Doormile AI — one dropdown step ||============================== // // ==============================|| Doormile AI — one dropdown step ||============================== //
@@ -70,7 +70,16 @@ const AIFlowStep = ({ step, onChoose, onCancel, draft, isBusy }) => {
? 'This tenant has no saved business locations — add one on the Tenants page before creating an order.' ? 'This tenant has no saved business locations — add one on the Tenants page before creating an order.'
: step.emptyHint || 'There’s nothing to choose from here.'} : step.emptyHint || 'There’s nothing to choose from here.'}
</Text> </Text>
<Button label="Cancel" variant="secondary" size="sm" onClick={onCancel} /> <div className="pt-1">
<Button
variant="outline"
size="sm"
className="border border-slate-200 bg-white hover:bg-slate-50 text-slate-700 font-medium text-xs px-3 h-8 rounded-lg shadow-2xs cursor-pointer"
onClick={onCancel}
>
Cancel
</Button>
</div>
</VStack> </VStack>
); );
} }
@@ -90,19 +99,26 @@ const AIFlowStep = ({ step, onChoose, onCancel, draft, isBusy }) => {
hasSearch={options.length > 8} hasSearch={options.length > 8}
isDisabled={isBusy} isDisabled={isBusy}
/> />
<HStack gap={1.5} padding={0}> <div className="flex items-center gap-2 pt-1">
<Button <Button
label="Continue" variant="default"
variant="primary"
size="sm" size="sm"
isDisabled={!value || isBusy} className="bg-slate-900 text-white hover:bg-black font-semibold text-xs px-3.5 h-8 rounded-lg shadow-xs cursor-pointer disabled:opacity-50"
// The whole option is passed back, not just its value — steps that disabled={!value || isBusy}
// pick a record (a location, a customer) need the record itself to
// fill several fields at once.
onClick={() => onChoose(value, chosen)} onClick={() => onChoose(value, chosen)}
/> >
<Button label="Cancel" variant="secondary" size="sm" isDisabled={isBusy} onClick={onCancel} /> Continue
</HStack> </Button>
<Button
variant="outline"
size="sm"
className="border border-slate-200 bg-white hover:bg-slate-50 text-slate-700 font-medium text-xs px-3 h-8 rounded-lg shadow-2xs cursor-pointer"
disabled={isBusy}
onClick={onCancel}
>
Cancel
</Button>
</div>
</VStack> </VStack>
); );
}; };

View File

@@ -6,8 +6,7 @@ import { LuChevronDown } from 'react-icons/lu';
import { HStack } from '@astryxdesign/core/HStack'; import { HStack } from '@astryxdesign/core/HStack';
import { VStack } from '@astryxdesign/core/VStack'; import { VStack } from '@astryxdesign/core/VStack';
import { Text } from '@astryxdesign/core/Text'; import { Text } from '@astryxdesign/core/Text';
import { IconButton } from '@astryxdesign/core/IconButton'; import { Button } from '@/components/ui/button';
import { Button } from '@astryxdesign/core/Button';
import { ChatToolCalls } from '@astryxdesign/core/Chat'; import { ChatToolCalls } from '@astryxdesign/core/Chat';
import { Spark, Metric, StatGrid, StateBlock, AnswerList } from './AIParts'; import { Spark, Metric, StatGrid, StateBlock, AnswerList } from './AIParts';
@@ -138,35 +137,46 @@ const AssistantMessage = ({ message, onCopy, onSubmitForm, onCancelAction, onCho
{['confirmCustomer', 'confirmOrder', 'confirmBulk', 'confirmAssign'].includes(message.form?.kind) && ( {['confirmCustomer', 'confirmOrder', 'confirmBulk', 'confirmAssign'].includes(message.form?.kind) && (
<VStack className="dai-action" gap={1.5} padding={0}> <VStack className="dai-action" gap={1.5} padding={0}>
{message.form.status === 'open' ? ( {message.form.status === 'open' ? (
<HStack gap={1.5} padding={0}> <div className="flex flex-wrap items-center gap-2 pt-1">
<Button <Button
label={ variant="default"
message.form.confirmLabel
? message.form.confirmLabel
: message.form.kind === 'confirmBulk'
? `Create ${message.form.draft?.rows?.length || 0} orders`
: message.form.kind === 'confirmOrder'
? 'Create order'
: message.form.kind === 'confirmAssign'
? 'Assign rider'
: 'Submit'
}
variant="primary"
size="sm" size="sm"
className="bg-slate-900 text-white hover:bg-black font-semibold text-xs px-3.5 h-8 rounded-lg shadow-xs cursor-pointer"
onClick={() => onSubmitForm(message, message.form.payload || message.form.draft)} onClick={() => onSubmitForm(message, message.form.payload || message.form.draft)}
/> >
<Button label="Cancel" variant="secondary" size="sm" onClick={() => onCancelAction(message)} /> {message.form.confirmLabel
? message.form.confirmLabel
: message.form.kind === 'confirmBulk'
? `Create ${message.form.draft?.rows?.length || 0} orders`
: message.form.kind === 'confirmOrder'
? 'Create order'
: message.form.kind === 'confirmAssign'
? 'Assign rider'
: 'Submit'}
</Button>
<Button
variant="outline"
size="sm"
className="border border-slate-200 bg-white hover:bg-slate-50 text-slate-700 font-medium text-xs px-3 h-8 rounded-lg shadow-2xs cursor-pointer"
onClick={() => onCancelAction(message)}
>
Cancel
</Button>
{/* A second, wider commit — used when rows were excluded by {/* A second, wider commit — used when rows were excluded by
default but including them is a legitimate choice. */} default but including them is a legitimate choice. */}
{message.form.altDraft && ( {message.form.altDraft && (
<Button <Button
label={message.form.altLabel || 'Include everything'}
variant="ghost" variant="ghost"
size="sm" size="sm"
className="text-slate-600 hover:bg-slate-100 font-medium text-xs px-3 h-8 rounded-lg cursor-pointer"
onClick={() => onSubmitForm(message, message.form.altDraft)} onClick={() => onSubmitForm(message, message.form.altDraft)}
/> >
{message.form.altLabel || 'Include everything'}
</Button>
)} )}
</HStack> </div>
) : ( ) : (
<Text className={`dai-action-note${message.form.status === 'done' ? ' dai-ok' : ''}`}> <Text className={`dai-action-note${message.form.status === 'done' ? ' dai-ok' : ''}`}>
{message.form.status === 'done' {message.form.status === 'done'

View File

@@ -4,7 +4,7 @@ import PropTypes from 'prop-types';
import { VStack } from '@astryxdesign/core/VStack'; import { VStack } from '@astryxdesign/core/VStack';
import { HStack } from '@astryxdesign/core/HStack'; import { HStack } from '@astryxdesign/core/HStack';
import { Text } from '@astryxdesign/core/Text'; import { Text } from '@astryxdesign/core/Text';
import { Button } from '@astryxdesign/core/Button'; import { Button } from '@/components/ui/button';
import { TextArea } from '@astryxdesign/core/TextArea'; import { TextArea } from '@astryxdesign/core/TextArea';
import { FileInput } from '@astryxdesign/core/FileInput'; import { FileInput } from '@astryxdesign/core/FileInput';
@@ -23,41 +23,38 @@ import { parseBulkFile, templateCsv, downloadCsv } from '@/lib/assistant/bulkFil
// //
// What is reported before the operator can continue, and why each matters: // What is reported before the operator can continue, and why each matters:
// • rows read — so a truncated or half-empty sheet is visible immediately // • rows read — so a truncated or half-empty sheet is visible immediately
// • columns NOT recognised, by name — an unread price column would otherwise // • unusable rows — bad phones, empty lines, missing fields; surfaced
// silently re-quote every row // up-front so the operator can fix the sheet instead of creating 9 good
// • rows repeated inside the file — the bulk endpoint has no idempotency key, // orders and wondering where the 10th went
// so a duplicate that gets through is a second real rider at one door // • columns ignored — confirms the resolver didn't mistake an unknown
// column for a standard one
const PLACEHOLDER = `Ramesh Kumar, 9876543210, 12 Cross Cut Rd Gandhipuram, 641012, Coimbatore, 120, 2 meal boxes
Priya N, 9876500011, 45 RS Puram, 641002, Coimbatore, 95, documents`;
const AIRowsStep = ({ onSubmit, onCancel, isBusy }) => { const AIRowsStep = ({ onSubmit, onCancel, isBusy }) => {
const [file, setFile] = useState(null); const [text, setText] = useState('');
const [fileParse, setFileParse] = useState(null);
const [fileError, setFileError] = useState(null);
const [raw, setRaw] = useState('');
const [isReading, setIsReading] = useState(false); const [isReading, setIsReading] = useState(false);
const [parsed, setParsed] = useState(null);
const pasted = raw.trim() ? parseBulkRows(raw) : null; const handlePasteChange = (val) => {
// A loaded file wins; clearing it falls back to whatever is in the textarea. setText(val);
const parsed = fileParse || pasted; if (!val.trim()) {
setParsed(null);
const handleFile = async (picked) => {
const chosen = Array.isArray(picked) ? picked[0] : picked;
setFile(chosen || null);
setFileError(null);
if (!chosen) {
setFileParse(null);
return; return;
} }
setParsed(parseBulkRows(val));
};
const handleFilePicked = async (files) => {
const file = Array.isArray(files) ? files[0] : files;
if (!file) return;
setIsReading(true); setIsReading(true);
try { try {
const result = await parseBulkFile(chosen); const res = await parseBulkFile(file);
setFileParse(result); setParsed(res);
setRaw(''); } catch {
} catch (err) { setParsed({
setFileParse(null); rows: [],
setFileError(err.message || 'Couldn’t read that file.'); errors: [{ row: 0, reason: 'Failed to read file' }],
ignoredColumns: []
});
} finally { } finally {
setIsReading(false); setIsReading(false);
} }
@@ -66,51 +63,46 @@ const AIRowsStep = ({ onSubmit, onCancel, isBusy }) => {
return ( return (
<VStack className="dai-action" gap={1.5} padding={0}> <VStack className="dai-action" gap={1.5} padding={0}>
<FileInput <FileInput
label="Order sheet" label="Upload spreadsheet (CSV, Excel)"
isLabelHidden accept=".csv,.xlsx,.xls,.tsv"
accept=".csv,.xls,.xlsx" onChange={handleFilePicked}
value={file}
onChange={handleFile}
isDisabled={isBusy || isReading} isDisabled={isBusy || isReading}
/> />
{!fileParse && ( <TextArea
<TextArea label="Or paste rows" rows={4} value={raw} onChange={setRaw} placeholder={PLACEHOLDER} isDisabled={isBusy} /> label="Or paste rows"
)} placeholder="Paste CSV / spreadsheet rows here…"
value={text}
rows={4}
onChange={handlePasteChange}
isDisabled={isBusy || isReading}
/>
{isReading && <Text className="dai-action-note">Reading the file…</Text>} {isReading && <Text className="dai-action-note">Reading the file…</Text>}
{fileError && <Text className="dai-field-err">{fileError}</Text>}
{parsed && ( {parsed && (
<VStack gap={0.5} padding={0}> <VStack gap={0.5} padding={0}>
<Text className="dai-action-note"> <Text className="dai-action-note">
{fileParse ? `${fileParse.sheetName} — ` : ''} ✓ Read {parsed.rows.length} usable row{parsed.rows.length === 1 ? '' : 's'}
{parsed.rows.length} complete row{parsed.rows.length === 1 ? '' : 's'}
{parsed.errors.length ? ` · ${parsed.errors.length} unusable` : ''}
</Text> </Text>
{parsed.ignoredColumns?.length > 0 && ( {parsed.ignoredColumns?.length > 0 && (
<Text className="dai-action-note">Columns I didn’t recognise, so didn’t use: {parsed.ignoredColumns.join(', ')}</Text> <Text className="dai-action-note">Columns I didn’t recognise, so didn’t use: {parsed.ignoredColumns.join(', ')}</Text>
)} )}
{parsed.errors?.length > 0 && (
{parsed.duplicates?.length > 0 && ( <VStack gap={0.25} padding={0}>
<Text className="dai-field-err"> <Text className="dai-field-err">
{parsed.duplicates.length} row{parsed.duplicates.length === 1 ? '' : 's'} repeat an earlier phone and address ( ⚠️ {parsed.errors.length} row{parsed.errors.length === 1 ? '' : 's'} couldn’t be read:
{parsed.duplicates.map((d) => `line ${d.line}`).join(', ')}) — they’d become separate orders. </Text>
</Text> {parsed.errors.slice(0, 5).map((e, idx) => (
<Text key={idx} className="dai-field-err">
Row {e.row}: {e.reason}
</Text>
))}
{parsed.errors.length > 5 && (
<Text className="dai-action-note">…and {parsed.errors.length - 5} more unusable rows.</Text>
)}
</VStack>
)} )}
{/* Keyed by line AND index: one row can fail several validations, so
`line` alone is not unique and React logged "two children with the
same key" for every repeat — seen live as a 1/2/3 cycle. Same
shape the list renderer in AIParts already uses. */}
{parsed.errors.slice(0, 5).map((e, i) => (
<Text key={`${e.line}-${i}`} className="dai-field-err">
Line {e.line}: {e.reason}
</Text>
))}
{parsed.errors.length > 5 && <Text className="dai-action-note">…and {parsed.errors.length - 5} more unusable rows.</Text>}
{parsed.rows.length > BULK_MAX && ( {parsed.rows.length > BULK_MAX && (
<Text className="dai-action-note"> <Text className="dai-action-note">
Over {BULK_MAX} — I’ll send them in {Math.ceil(parsed.rows.length / BULK_MAX)} batches. Over {BULK_MAX} — I’ll send them in {Math.ceil(parsed.rows.length / BULK_MAX)} batches.
@@ -119,23 +111,35 @@ const AIRowsStep = ({ onSubmit, onCancel, isBusy }) => {
</VStack> </VStack>
)} )}
<HStack gap={1.5} padding={0}> <div className="flex flex-wrap items-center gap-2 pt-1">
<Button <Button
label={parsed?.rows.length ? `Use these ${parsed.rows.length} rows` : 'Continue'} variant="default"
variant="primary"
size="sm" size="sm"
isDisabled={!parsed?.rows.length || isBusy || isReading} className="bg-slate-900 text-white hover:bg-black font-semibold text-xs px-3.5 h-8 rounded-lg shadow-xs cursor-pointer disabled:opacity-50"
disabled={!parsed?.rows.length || isBusy || isReading}
onClick={() => onSubmit(parsed)} onClick={() => onSubmit(parsed)}
/> >
<Button label="Cancel" variant="secondary" size="sm" isDisabled={isBusy} onClick={onCancel} /> {parsed?.rows.length ? `Use these ${parsed.rows.length} rows` : 'Continue'}
</Button>
<Button
variant="outline"
size="sm"
className="border border-slate-200 bg-white hover:bg-slate-50 text-slate-700 font-medium text-xs px-3 h-8 rounded-lg shadow-2xs cursor-pointer"
disabled={isBusy}
onClick={onCancel}
>
Cancel
</Button>
<Button <Button
label="Template"
variant="ghost" variant="ghost"
size="sm" size="sm"
tooltip="Download a blank sheet with the exact column titles I read" className="text-slate-600 hover:bg-slate-100 font-medium text-xs px-3 h-8 rounded-lg cursor-pointer"
title="Download a blank sheet with the exact column titles I read"
onClick={() => downloadCsv('doormile-bulk-orders-template.csv', templateCsv())} onClick={() => downloadCsv('doormile-bulk-orders-template.csv', templateCsv())}
/> >
</HStack> Template
</Button>
</div>
<Text className="dai-action-note"> <Text className="dai-action-note">
Pasted rows: name, phone, address, pincode, city, price, description — comma, tab or pipe separated. Leave the price blank and I’ll Pasted rows: name, phone, address, pincode, city, price, description — comma, tab or pipe separated. Leave the price blank and I’ll

View File

@@ -108,6 +108,29 @@ export function ZoneProvider({ children }) {
const currentHub = selectedZone; const currentHub = selectedZone;
const targetHubId = String(currentHub.hubid); const targetHubId = String(currentHub.hubid);
// Rider matching: riders belong to the tenant fleet and serve all tenant kitchen hubs
const isRiderItem =
item.milerprofileid != null ||
item.authname != null ||
(item.userid != null && item.availabilitystatus != null);
if (isRiderItem) {
if (isTenantUser) {
return true;
}
if (item.applocationid != null && (String(item.applocationid) === targetHubId || String(item.applocationid) === String(currentHub.applocationid))) {
return true;
}
if (item.hubid != null && String(item.hubid) === targetHubId) {
return true;
}
const hubCity = String(currentHub.city || '').trim().toLowerCase();
if (hubCity && String(item.city || '').trim().toLowerCase() === hubCity) {
return true;
}
return false;
}
// 1. Direct hub/location id match // 1. Direct hub/location id match
const itemHubId = const itemHubId =
item.hubid ?? item.hubid ??

View File

@@ -953,40 +953,50 @@ body.dai-docked .astryx-layout-content {
} }
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
Write-action confirm card Write-action confirm card & buttons
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */
.dai-root .dai-action { .dai-root .dai-action {
padding: 10px 11px; padding: 12px 14px;
border: 1px solid var(--dai-border-strong); border: 1px solid #e2e8f0;
border-radius: 5px; border-radius: 12px;
background: var(--dai-surface-alt); background: #ffffff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
display: flex;
flex-direction: column;
gap: 10px;
} }
.dai-root .dai-action-icon { .dai-root .dai-action-icon {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex: 0 0 auto; flex: 0 0 auto;
width: 26px; width: 28px;
height: 26px; height: 28px;
border-radius: 5px; border-radius: 8px;
color: #4f46e5; color: #4f46e5;
background: rgba(99, 102, 241, 0.1); background: rgba(99, 102, 241, 0.1);
} }
.dai-root .dai-action-summary { .dai-root .dai-action-summary {
font-size: 13px; font-size: 13px;
line-height: 1.35; line-height: 1.4;
font-weight: 550; font-weight: 600;
color: var(--dai-text); color: var(--dai-text);
} }
.dai-root .dai-action-note { .dai-root .dai-action-note {
font-size: 12px; font-size: 12px;
color: var(--dai-text-secondary); color: #64748b;
line-height: 1.4;
} }
.dai-root .dai-ok { .dai-root .dai-ok {
color: #047857; color: #047857;
font-weight: 500;
} }
.dai-root .dai-err { .dai-root .dai-err {
color: #b91c1c; color: #b91c1c;
font-weight: 500;
}
.dai-root .dai-action button {
cursor: pointer;
} }
/* -------------------------------------------------------------------------- /* --------------------------------------------------------------------------
Overflow containment Overflow containment

View File

@@ -279,10 +279,7 @@ export default function Deliveries() {
sortable: true, sortable: true,
cell: (row) => ( cell: (row) => (
<div className="flex flex-col items-start gap-0.5"> <div className="flex flex-col items-start gap-0.5">
<StatusBadge status={row.consignmentstatus || row.orderstatus} dot size="sm" /> <StatusBadge status={row.orderstatus} dot size="sm" />
{/* Where the value came from. Without this, a row reading "Active"
while the booking still says Converted_To_Consignment looks
exactly like a bug. */}
{row.statusfromconsignment ? ( {row.statusfromconsignment ? (
<span className="text-[10px] text-ink-4">from consignment</span> <span className="text-[10px] text-ink-4">from consignment</span>
) : null} ) : null}