feat(orders): progress stepper + fix field_change label
This commit is contained in:
parent
15b69102b0
commit
464cc57eb9
|
|
@ -60,6 +60,7 @@ const ACTION_LABELS = {
|
|||
delivery_status_change: "Изменение статуса доставки",
|
||||
notification_status_change: "Изменение статуса уведомления",
|
||||
status_change: "Изменение статуса",
|
||||
field_change: "Изменение данных заказа",
|
||||
};
|
||||
|
||||
const SMS_CAMPAIGN_LABELS = {
|
||||
|
|
@ -113,6 +114,134 @@ const formatChanges = (changes) => {
|
|||
return lines;
|
||||
};
|
||||
|
||||
// ─── Progress Stepper ────────────────────────────────────────────────
|
||||
// Visual horizontal stepper showing where the client is in the journey.
|
||||
// Stages: Добавлен → SMS → Согласовано → Водитель (delivery only) → Доставлен
|
||||
|
||||
const STAGES_DELIVERY = [
|
||||
{ key: "created", icon: "📥", label: "Добавлен" },
|
||||
{ key: "sms", icon: "📨", label: "SMS" },
|
||||
{ key: "agreed", icon: "✅", label: "Согласовано" },
|
||||
{ key: "driver", icon: "🚚", label: "Водитель" },
|
||||
{ key: "delivered", icon: "📦", label: "Доставлено" },
|
||||
];
|
||||
|
||||
const STAGES_PICKUP = [
|
||||
{ key: "created", icon: "📥", label: "Добавлен" },
|
||||
{ key: "sms", icon: "📨", label: "SMS" },
|
||||
{ key: "agreed", icon: "✅", label: "Согласовано" },
|
||||
{ key: "picked_up", icon: "📦", label: "Вывезено" },
|
||||
];
|
||||
|
||||
const getActiveStageIndex = (order) => {
|
||||
const deliveryStatus = order?.deliveryStatus || order?.delivery_status || "";
|
||||
const notifStatus = order?.notificationStatus || order?.notification_status || "";
|
||||
const firstSms = order?.firstSmsSentAt || order?.first_sms_sent_at;
|
||||
const isPickup = (order?.deliveryType || order?.delivery_type) === "pickup";
|
||||
const stages = isPickup ? STAGES_PICKUP : STAGES_DELIVERY;
|
||||
|
||||
// Terminal states
|
||||
if (deliveryStatus === "cancelled") return -1; // special: show all grey
|
||||
if (deliveryStatus === "problem") return -2; // special: show problem indicator
|
||||
if (deliveryStatus === "paid_storage") return -3;
|
||||
|
||||
// Final delivery stage
|
||||
if (isPickup) {
|
||||
if (deliveryStatus === "picked_up") return stages.length - 1;
|
||||
} else {
|
||||
if (deliveryStatus === "delivered") return stages.length - 1;
|
||||
}
|
||||
|
||||
// Driver assigned (delivery only)
|
||||
if (!isPickup && ["driver_assigned", "loaded", "on_route"].includes(deliveryStatus)) {
|
||||
return 3; // driver stage
|
||||
}
|
||||
|
||||
// Agreed
|
||||
if (deliveryStatus === "agreed" || notifStatus === "confirmed") {
|
||||
return 2;
|
||||
}
|
||||
|
||||
// SMS sent
|
||||
if (firstSms || ["first_sms_sent", "second_sms_sent", "confirmed"].includes(notifStatus)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Just created
|
||||
return 0;
|
||||
};
|
||||
|
||||
const ProgressStepper = ({ order }) => {
|
||||
const isPickup = (order?.deliveryType || order?.delivery_type) === "pickup";
|
||||
const stages = isPickup ? STAGES_PICKUP : STAGES_DELIVERY;
|
||||
const activeIdx = getActiveStageIndex(order);
|
||||
|
||||
// Special states
|
||||
if (activeIdx === -1) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-xl bg-[rgba(239,68,68,0.08)] px-3 py-2">
|
||||
<span className="text-sm">❌</span>
|
||||
<span className="text-sm font-semibold text-[var(--color-danger)]">Заказ отменён</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (activeIdx === -2) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-xl bg-[rgba(239,68,68,0.08)] px-3 py-2">
|
||||
<span className="text-sm">⚠️</span>
|
||||
<span className="text-sm font-semibold text-[var(--color-danger)]">Проблема с заказом</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (activeIdx === -3) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 rounded-xl bg-[rgba(245,158,11,0.08)] px-3 py-2">
|
||||
<span className="text-sm">📦</span>
|
||||
<span className="text-sm font-semibold text-[var(--color-warning)]">Платное хранение</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{stages.map((stage, idx) => {
|
||||
const isDone = idx < activeIdx;
|
||||
const isActive = idx === activeIdx;
|
||||
const isFuture = idx > activeIdx;
|
||||
return (
|
||||
<React.Fragment key={stage.key}>
|
||||
{idx > 0 && (
|
||||
<div className={`h-0.5 flex-1 min-w-[12px] rounded-full transition-colors ${
|
||||
isDone ? "bg-[var(--color-accent)]" : "bg-[var(--color-border)]"
|
||||
}`} />
|
||||
)}
|
||||
<div className="flex flex-col items-center gap-1 flex-shrink-0">
|
||||
<div className={`flex h-7 w-7 items-center justify-center rounded-full text-xs transition-all ${
|
||||
isActive
|
||||
? "bg-[var(--color-accent)] text-white ring-2 ring-[var(--color-accent)] ring-offset-2 ring-offset-[var(--color-surface)]"
|
||||
: isDone
|
||||
? "bg-[var(--color-accent-soft)] text-[var(--color-accent)]"
|
||||
: "bg-[var(--color-surface-strong)] text-[var(--color-text-muted)]"
|
||||
}`}>
|
||||
{isDone ? "✓" : stage.icon}
|
||||
</div>
|
||||
<span className={`text-[10px] whitespace-nowrap ${
|
||||
isActive
|
||||
? "font-bold text-[var(--color-text)]"
|
||||
: isDone
|
||||
? "font-medium text-[var(--color-accent)]"
|
||||
: "text-[var(--color-text-muted)]"
|
||||
}`}>
|
||||
{stage.label}
|
||||
</span>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const OrderHistoryTimeline = ({ order, userRole }) => {
|
||||
const [history, setHistory] = useState(null);
|
||||
const [smsLogs, setSmsLogs] = useState([]);
|
||||
|
|
@ -211,7 +340,9 @@ export const OrderHistoryTimeline = ({ order, userRole }) => {
|
|||
if (allEvents.length === 0) return null;
|
||||
|
||||
return (
|
||||
<ol className="relative space-y-3 pl-4">
|
||||
<div className="space-y-4">
|
||||
<ProgressStepper order={order} />
|
||||
<ol className="relative space-y-3 pl-4">
|
||||
{allEvents.map((ev, idx) => (
|
||||
<li key={ev.id ?? idx} className="relative border-l border-[var(--color-border)] pl-4">
|
||||
<span className="absolute -left-[5px] top-1 h-2.5 w-2.5 rounded-full bg-[var(--color-accent)]" aria-hidden />
|
||||
|
|
@ -253,7 +384,8 @@ export const OrderHistoryTimeline = ({ order, userRole }) => {
|
|||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue