CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 24s
CI / Build server (push) Successful in 29s
CI / Build client (push) Successful in 40s
CI / Playwright E2E tests (push) Successful in 1m46s
CI / Build and push Docker image (push) Successful in 48s
CI / Notify (push) Successful in 3s
Wolt hlásí stav podniku, ne rozvozu — status zůstane na "ready" i celou dobu, co kurýr objednávku veze. Vyzvednutí proto poznáme podle pickup_eta: dokud je v budoucnosti, jídlo ještě čeká v podniku, po vyzvednutí se ustálí na skutečném čase. Dřív se stav odvozoval jen z příznaků kurýra, takže objednávka zůstala viset na kroku "Vyzvedávání", i když už byla na cestě. Kurýr má nově tři odvozené stavy: assigned (ještě nevyzvedl), delivering (veze ji k nám) a delivering_other (veze ji, ale nejdřív doručuje cizí objednávku — Wolt objednávky spojuje a kurýr se pak na mapě vzdaluje). Oba stavy delivering* znamenají krok "Na cestě", tooltip u nich vysvětlí objížďku.
154 lines
6.1 KiB
TypeScript
154 lines
6.1 KiB
TypeScript
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
|
import { TrackingProvider } from '../../../types';
|
|
import './OrderProgress.scss';
|
|
|
|
const STEPS = ['Přijato', 'Příprava', 'Vyzvedávání', 'Na cestě', 'Doručeno'];
|
|
|
|
/** Lidský název rozvozové služby do tooltipu. */
|
|
export const PROVIDER_LABEL: Record<TrackingProvider, string> = {
|
|
bolt: 'Bolt Food',
|
|
wolt: 'Wolt',
|
|
};
|
|
|
|
/**
|
|
* Známé stavy objednávky z Bolt API → index kroku ve stepperu.
|
|
* Pozor: waiting_delivery znamená "jídlo čeká v podniku na vyzvednutí",
|
|
* nikoli "na cestě" — tu signalizuje až stav kurýra (picked_up apod.).
|
|
* Krok „Vyzvedávání" je vyhrazen pro kombinaci „kurýr u podniku + jídlo hotové"
|
|
* (viz logika níže), samotný order_state ho neudělí — jinak bychom hlásili
|
|
* vyzvedávání i když se ještě peče (chování, na které si Bolt sám občas stěžuje).
|
|
*/
|
|
const BOLT_STATE_TO_STEP: Record<string, number> = {
|
|
created: 0,
|
|
pending: 0,
|
|
waiting_acceptance: 0,
|
|
accepted: 0,
|
|
waiting_preparation: 0,
|
|
preparing: 1,
|
|
waiting_delivery: 1,
|
|
ready_for_pickup: 1,
|
|
waiting_courier: 1,
|
|
waiting_pickup: 1,
|
|
picked_up: 3,
|
|
in_delivery: 3,
|
|
delivering: 3,
|
|
heading_to_client: 3,
|
|
delivered: 4,
|
|
finished: 4,
|
|
};
|
|
|
|
/** Stavy kurýra z Bolt API → index kroku. Kurýr u podniku ještě neznamená "na cestě". */
|
|
const BOLT_COURIER_STATE_TO_STEP: Record<string, number> = {
|
|
matched: 0,
|
|
accepted: 0,
|
|
heading_to_provider: 1,
|
|
arrived_to_provider: 1,
|
|
picked_up: 3,
|
|
heading_to_client: 3,
|
|
delivering: 3,
|
|
arrived_to_client: 3,
|
|
delivered: 4,
|
|
};
|
|
|
|
/**
|
|
* Známé stavy objednávky z Wolt tracking API → index kroku ve stepperu.
|
|
* Pozor: Wolt hlásí stav podniku, ne rozvozu — "ready" (jídlo hotové) zůstává
|
|
* i celou dobu, co kurýr objednávku veze. Kroky „Vyzvedávání" a „Na cestě" proto
|
|
* neurčuje status, ale odvozený stav kurýra (viz trackingProviders.ts).
|
|
*/
|
|
const WOLT_STATE_TO_STEP: Record<string, number> = {
|
|
received: 0,
|
|
acknowledged: 0,
|
|
confirmed: 0,
|
|
waiting: 0,
|
|
production: 1,
|
|
preparing: 1,
|
|
ready: 1,
|
|
picked_up: 3,
|
|
transport: 3,
|
|
delivering: 3,
|
|
delivered: 4,
|
|
};
|
|
|
|
/** Popis odvozeného stavu kurýra u Woltu do tooltipu. */
|
|
const WOLT_COURIER_INFO: Record<string, string> = {
|
|
assigned: 'kurýr přiřazen',
|
|
delivering: 'kurýr veze objednávku',
|
|
delivering_other: 'kurýr veze objednávku, ale nejdřív doručuje jinou',
|
|
};
|
|
|
|
/** Stavy, ve kterých už jídlo čeká připravené na kurýra. */
|
|
const PICKUP_READY_STATES: Record<TrackingProvider, Set<string>> = {
|
|
bolt: new Set(['waiting_delivery', 'ready_for_pickup', 'waiting_courier', 'waiting_pickup']),
|
|
wolt: new Set(['ready']),
|
|
};
|
|
|
|
/** Neznámé stavy se mapují heuristicky podle klíčových slov. */
|
|
function stepForOrderState(provider: TrackingProvider, state: string): number | 'cancelled' {
|
|
const s = state.toLowerCase();
|
|
const known = provider === 'wolt' ? WOLT_STATE_TO_STEP : BOLT_STATE_TO_STEP;
|
|
if (s in known) return known[s];
|
|
if (/cancel|reject|fail/.test(s)) return 'cancelled';
|
|
if (/delivered|finished/.test(s)) return 4;
|
|
if (/accept|pending|created|received/.test(s)) return 0;
|
|
if (/^waiting|prepar|ready|cook|production/.test(s)) return 1;
|
|
if (/picked|delivering|heading_to_client|transport/.test(s)) return 3;
|
|
return 0;
|
|
}
|
|
|
|
function stepForCourierState(provider: TrackingProvider, state?: string): number {
|
|
if (!state) return 0;
|
|
const s = state.toLowerCase();
|
|
// Wolt má jen odvozené stavy: 'assigned' o kroku nic neříká, 'delivering'
|
|
// i 'delivering_other' znamenají, že kurýr naši objednávku už veze
|
|
if (provider === 'wolt') return s.startsWith('delivering') ? 3 : 0;
|
|
if (s in BOLT_COURIER_STATE_TO_STEP) return BOLT_COURIER_STATE_TO_STEP[s];
|
|
if (/picked|client|delivering|transport/.test(s)) return 3;
|
|
return 0;
|
|
}
|
|
|
|
interface Props {
|
|
/** Rozvozová služba, ze které stav pochází */
|
|
provider: TrackingProvider;
|
|
/** Raw stav objednávky (Bolt order_state, Wolt status) */
|
|
state: string;
|
|
/** Raw stav kurýra (Bolt courier.state, Wolt 'assigned') */
|
|
courierState?: string;
|
|
/** Zda sledování stále běží (skupina má trackingCode) */
|
|
tracking: boolean;
|
|
}
|
|
|
|
/** Mini progress stepper se stavem objednávky u rozvozové služby. */
|
|
export default function OrderProgress({ provider, state, courierState, tracking }: Props) {
|
|
const label = PROVIDER_LABEL[provider] ?? provider;
|
|
const orderStep = stepForOrderState(provider, state);
|
|
if (orderStep === 'cancelled') {
|
|
return <small className="text-danger">Objednávka {label} byla zrušena</small>;
|
|
}
|
|
// Stav kurýra může krok jen zpřesnit dopředu (např. waiting_delivery + picked_up → Na cestě)
|
|
let step = Math.max(orderStep, stepForCourierState(provider, courierState));
|
|
// Vyzvedávání = kurýr u podniku a jídlo skutečně hotové. Sám arrived_to_provider
|
|
// (bez toho, že by jídlo bylo hotové) nestačí — viz komentář u BOLT_STATE_TO_STEP.
|
|
const courierAtProvider = provider === 'wolt'
|
|
? !!courierState
|
|
: courierState?.toLowerCase() === 'arrived_to_provider';
|
|
const foodReadyForPickup = PICKUP_READY_STATES[provider]?.has(state.toLowerCase());
|
|
if (step < 3 && courierAtProvider && foodReadyForPickup) step = 2;
|
|
const courierInfo = provider === 'wolt'
|
|
? WOLT_COURIER_INFO[courierState ?? ''] ?? 'kurýr přiřazen'
|
|
: `kurýr: ${courierState}`;
|
|
const rawInfo = courierState ? `${state} / ${courierInfo}` : state;
|
|
return (
|
|
<OverlayTrigger overlay={<Tooltip>Stav z {label}: {rawInfo}</Tooltip>}>
|
|
<div className={`tracking-progress${tracking && step < 4 ? ' live' : ''}`}>
|
|
{STEPS.map((stepLabel, i) => (
|
|
<div key={stepLabel} className={`tracking-step${i <= step ? ' done' : ''}${i === step ? ' active' : ''}`}>
|
|
<div className="tracking-dot" />
|
|
<div className="tracking-label">{stepLabel}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</OverlayTrigger>
|
|
);
|
|
}
|