192 lines
7.7 KiB
JavaScript
192 lines
7.7 KiB
JavaScript
import React from "react";
|
||
import {
|
||
filterOrderGroups,
|
||
getOrderGroupDeliveryHalfDay,
|
||
getOrderGroupDeliveryStatusLabel,
|
||
getOrderGroupDeliveryStatusTone,
|
||
ORDER_GROUP_DELIVERY_HALF_DAY_OPTIONS,
|
||
DRIVER_VISIBLE_DELIVERY_STATUSES,
|
||
isOrderGroupVisibleToDriver,
|
||
groupOrderGroupsByDate,
|
||
} from "../../services/orderGroupViews";
|
||
import { Badge } from "../UI/Badge";
|
||
import { Input } from "../UI/Input";
|
||
import { Select } from "../UI/Select";
|
||
import { Panel } from "../UI/Panel";
|
||
|
||
const DRIVER_DELIVERY_STATUS_OPTIONS = [
|
||
{ value: "all", label: "Все статусы" },
|
||
...DRIVER_VISIBLE_DELIVERY_STATUSES.map((status) => ({
|
||
value: status,
|
||
label: getOrderGroupDeliveryStatusLabel(status),
|
||
})),
|
||
];
|
||
|
||
export const DriverDeliveryPlanner = ({ orderGroups = [], onOpenOrder }) => {
|
||
const [filters, setFilters] = React.useState({
|
||
dateFrom: "",
|
||
dateTo: "",
|
||
deliveryHalfDay: "all",
|
||
deliveryStatus: "all",
|
||
});
|
||
|
||
const agreedOrderGroups = React.useMemo(
|
||
() => orderGroups.filter((group) => isOrderGroupVisibleToDriver(group)),
|
||
[orderGroups],
|
||
);
|
||
|
||
const filteredOrderGroups = React.useMemo(
|
||
() =>
|
||
filterOrderGroups(agreedOrderGroups, {
|
||
dateFrom: filters.dateFrom,
|
||
dateTo: filters.dateTo,
|
||
deliveryHalfDay: filters.deliveryHalfDay,
|
||
deliveryStatus: filters.deliveryStatus,
|
||
}),
|
||
[agreedOrderGroups, filters.dateFrom, filters.dateTo, filters.deliveryHalfDay, filters.deliveryStatus],
|
||
);
|
||
|
||
const groupedOrderGroups = React.useMemo(
|
||
() => groupOrderGroupsByDate(filteredOrderGroups),
|
||
[filteredOrderGroups],
|
||
);
|
||
|
||
return (
|
||
<div className="space-y-4">
|
||
<Panel className="space-y-3 p-5">
|
||
<div className="space-y-4">
|
||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||
<div>
|
||
<h3 className="text-lg font-semibold">Мои доставки</h3>
|
||
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||
Показываем только согласованные к доставке группы. Можно сузить список по дате и половине дня.
|
||
</p>
|
||
</div>
|
||
<Badge tone="neutral">{filteredOrderGroups.length}</Badge>
|
||
</div>
|
||
|
||
<div className="grid gap-3 md:grid-cols-[repeat(4,minmax(0,1fr))]">
|
||
<label className="flex min-w-0 flex-col gap-2">
|
||
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||
Дата от
|
||
</span>
|
||
<Input
|
||
type="date"
|
||
value={filters.dateFrom}
|
||
onChange={(event) => setFilters((current) => ({ ...current, dateFrom: event.target.value }))}
|
||
/>
|
||
</label>
|
||
<label className="flex min-w-0 flex-col gap-2">
|
||
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||
Дата до
|
||
</span>
|
||
<Input
|
||
type="date"
|
||
value={filters.dateTo}
|
||
onChange={(event) => setFilters((current) => ({ ...current, dateTo: event.target.value }))}
|
||
/>
|
||
</label>
|
||
<label className="flex min-w-0 flex-col gap-2">
|
||
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||
Время суток
|
||
</span>
|
||
<Select
|
||
value={filters.deliveryHalfDay}
|
||
onChange={(event) =>
|
||
setFilters((current) => ({ ...current, deliveryHalfDay: event.target.value }))
|
||
}
|
||
>
|
||
{ORDER_GROUP_DELIVERY_HALF_DAY_OPTIONS.map((option) => (
|
||
<option key={option.value} value={option.value}>
|
||
{option.label}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</label>
|
||
<label className="flex min-w-0 flex-col gap-2">
|
||
<span className="text-xs font-semibold uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||
Статус
|
||
</span>
|
||
<Select
|
||
value={filters.deliveryStatus}
|
||
onChange={(event) =>
|
||
setFilters((current) => ({ ...current, deliveryStatus: event.target.value }))
|
||
}
|
||
>
|
||
{DRIVER_DELIVERY_STATUS_OPTIONS.map((option) => (
|
||
<option key={option.value} value={option.value}>
|
||
{option.label}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</Panel>
|
||
|
||
{groupedOrderGroups.length ? (
|
||
groupedOrderGroups.map((group) => (
|
||
<Panel key={group.date} className="space-y-4 p-5">
|
||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||
<div>
|
||
<h4 className="text-lg font-semibold capitalize">
|
||
{new Date(`${group.date}T12:00:00`).toLocaleDateString("ru-RU", {
|
||
day: "numeric",
|
||
month: "long",
|
||
weekday: "long",
|
||
})}
|
||
</h4>
|
||
<p className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||
{group.items.length} {group.items.length === 1 ? "группа" : "группы"}
|
||
</p>
|
||
</div>
|
||
<Badge tone="neutral">{group.date}</Badge>
|
||
</div>
|
||
|
||
<div className="grid gap-3">
|
||
{group.items.map((item) => (
|
||
<button
|
||
key={item.id}
|
||
type="button"
|
||
className="rounded-[24px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 text-left transition hover:bg-[var(--color-accent-soft)]"
|
||
onClick={() => onOpenOrder?.(item.id)}
|
||
>
|
||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||
<div>
|
||
<div className="font-medium text-[var(--color-text)]">
|
||
{item.displayTitle || item.customerName || item.groupKey}
|
||
</div>
|
||
<div className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||
{item.customerDate} · {item.customerPhone}
|
||
{getOrderGroupDeliveryHalfDay(item) ? ` · ${getOrderGroupDeliveryHalfDay(item)}` : ""}
|
||
</div>
|
||
</div>
|
||
<Badge tone={getOrderGroupDeliveryStatusTone(item.deliveryStatus || item.delivery_status)}>
|
||
{getOrderGroupDeliveryStatusLabel(item.deliveryStatus || item.delivery_status)}
|
||
</Badge>
|
||
</div>
|
||
|
||
<div className="mt-3 grid gap-2 text-sm text-[var(--color-text-muted)] md:grid-cols-3">
|
||
<div>{item.orderNumbers?.[0] || "Номера не указаны"}</div>
|
||
<div>
|
||
{item.readyCount || 0}/{item.ordersCount || 0} готово
|
||
</div>
|
||
<div>{item.smsSentAt ? "SMS отправлено" : "SMS не отправлено"}</div>
|
||
</div>
|
||
</button>
|
||
))}
|
||
</div>
|
||
</Panel>
|
||
))
|
||
) : (
|
||
<Panel className="p-6">
|
||
<h4 className="text-lg font-semibold">Доставки не найдены</h4>
|
||
<p className="mt-2 text-sm text-[var(--color-text-muted)]">
|
||
Сейчас у вас нет назначенных групп доставки.
|
||
</p>
|
||
</Panel>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|