feat: zobrazení stavu objednávky Bolt Food jako progress stepper
CI / Generate TypeScript types (push) Successful in 9s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 35s
CI / Playwright E2E tests (push) Successful in 1m22s
CI / Build and push Docker image (push) Successful in 41s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 9s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 35s
CI / Playwright E2E tests (push) Successful in 1m22s
CI / Build and push Docker image (push) Successful in 41s
CI / Notify (push) Successful in 2s
Jak to funguje: - OrderGroup má nové pole boltOrderState (raw order_state z Bolt API). Polling scheduler ho ukládá při každé změně a rozesílá přes Socket.io, takže stepper se posouvá živě všem uživatelům. - Komponenta BoltOrderProgress vykresluje čtyři kroky (Přijato → Příprava → Na cestě → Doručeno) pod časy skupiny. Známé stavy se mapují explicitně, neznámé heuristikou podle klíčových slov, zrušená objednávka se zobrazí červeně. Tooltip ukazuje raw stav, aktivní krok pulzuje, dokud sledování běží. - Po doručení (nebo zmizení objednávky z API) se token smaže, ale boltOrderState zůstává "delivered" — dokončený stepper je vidět po zbytek dne. Vynuluje se při změně/zrušení odkazu nebo návratu skupiny do stavu uzamčeno. - Nastavení odkazu nově spustí okamžitý poll, stepper se tak objeví do vteřiny místo čekání na další tik scheduleru. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
.bolt-progress {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
max-width: 400px;
|
||||
|
||||
.bolt-step {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 64px;
|
||||
|
||||
.bolt-dot {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: var(--luncher-border, #ced4da);
|
||||
}
|
||||
|
||||
.bolt-label {
|
||||
margin-top: 2px;
|
||||
font-size: 0.7em;
|
||||
color: var(--luncher-text-muted, #6c757d);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Spojnice k předchozímu kroku
|
||||
&:not(:first-child)::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 50%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--luncher-border, #ced4da);
|
||||
}
|
||||
|
||||
&.done {
|
||||
.bolt-dot {
|
||||
background: var(--bs-success, #198754);
|
||||
}
|
||||
|
||||
&:not(:first-child)::before {
|
||||
background: var(--bs-success, #198754);
|
||||
}
|
||||
}
|
||||
|
||||
&.active .bolt-label {
|
||||
font-weight: 600;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
// Pulzování aktivního kroku, dokud sledování běží
|
||||
&.live .bolt-step.active .bolt-dot {
|
||||
animation: bolt-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bolt-pulse {
|
||||
0%, 100% {
|
||||
box-shadow: 0 0 0 0 rgba(25, 135, 84, 0.5);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 5px rgba(25, 135, 84, 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import './BoltOrderProgress.scss';
|
||||
|
||||
const STEPS = ['Přijato', 'Příprava', 'Na cestě', 'Doručeno'];
|
||||
|
||||
/** Známé stavy objednávky z Bolt API → index kroku ve stepperu. */
|
||||
const STATE_TO_STEP: Record<string, number> = {
|
||||
created: 0,
|
||||
pending: 0,
|
||||
accepted: 0,
|
||||
waiting_preparation: 0,
|
||||
preparing: 1,
|
||||
ready_for_pickup: 1,
|
||||
waiting_courier: 1,
|
||||
courier_assigned: 1,
|
||||
picked_up: 2,
|
||||
in_delivery: 2,
|
||||
delivering: 2,
|
||||
heading_to_client: 2,
|
||||
delivered: 3,
|
||||
finished: 3,
|
||||
};
|
||||
|
||||
/** Neznámé stavy se mapují heuristicky podle klíčových slov. */
|
||||
function stepForState(state: string): number | 'cancelled' {
|
||||
const s = state.toLowerCase();
|
||||
if (s in STATE_TO_STEP) return STATE_TO_STEP[s];
|
||||
if (/cancel|reject|fail/.test(s)) return 'cancelled';
|
||||
if (/delivered|finished/.test(s)) return 3;
|
||||
if (/courier|picked|delivery|transport/.test(s)) return 2;
|
||||
if (/prepar|ready|cook/.test(s)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** Raw order_state z Bolt API (např. waiting_preparation) */
|
||||
state: string;
|
||||
/** Zda sledování stále běží (skupina má boltTrackingToken) */
|
||||
tracking: boolean;
|
||||
}
|
||||
|
||||
/** Mini progress stepper se stavem objednávky Bolt Food. */
|
||||
export default function BoltOrderProgress({ state, tracking }: Props) {
|
||||
const step = stepForState(state);
|
||||
if (step === 'cancelled') {
|
||||
return <small className="text-danger">Objednávka Bolt byla zrušena</small>;
|
||||
}
|
||||
return (
|
||||
<OverlayTrigger overlay={<Tooltip>Stav z Bolt Food: {state}</Tooltip>}>
|
||||
<div className={`bolt-progress${tracking && step < 3 ? ' live' : ''}`}>
|
||||
{STEPS.map((label, i) => (
|
||||
<div key={label} className={`bolt-step${i <= step ? ' done' : ''}${i === step ? ' active' : ''}`}>
|
||||
<div className="bolt-dot" />
|
||||
<div className="bolt-label">{label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
);
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import StoreAdminModal from '../components/modals/StoreAdminModal';
|
||||
import PayForGroupModal from '../components/modals/PayForGroupModal';
|
||||
import EditGroupFeesModal from '../components/modals/EditGroupFeesModal';
|
||||
import PendingPayments from '../components/PendingPayments';
|
||||
import BoltOrderProgress from '../components/BoltOrderProgress';
|
||||
|
||||
const SLOT = MealSlot.EXTRA;
|
||||
const TIME_REGEX = /^([01]\d|2[0-3]):[0-5]\d$/;
|
||||
@@ -797,6 +798,11 @@ export default function OrderGroupsPage() {
|
||||
})()}
|
||||
</div>
|
||||
)}
|
||||
{group.boltOrderState && (
|
||||
<div className="mt-2">
|
||||
<BoltOrderProgress state={group.boltOrderState} tracking={!!group.boltTrackingToken} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Card.Body>
|
||||
|
||||
Reference in New Issue
Block a user