feat: Тип column replaces Проблема, red highlight for problem rows, delivery type filter, clean status options
- OrdersTable: replaced 'Проблема' column with 'Тип' (Самовывоз/Доставка) - Problem rows highlighted red (bg rgba danger 10%) - Mobile cards also show Тип label - OrderFilters: added deliveryType dropdown (Доставка/Самовывоз) - filterOrderGroups: filter by deliveryType - useOrderGroups: deliveryType in default filters state - Cleaned ORDER_GROUP_DISPLAY_STATUS_OPTIONS: removed 'Повторное SMS' and 'SMS отправлены' (not used)
This commit is contained in:
parent
8d3d8dc0fe
commit
54386e9710
|
|
@ -105,6 +105,16 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities =
|
||||||
</select>
|
</select>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<select
|
||||||
|
value={filters.deliveryType || ""}
|
||||||
|
onChange={(e) => updateFilter("deliveryType", e.target.value)}
|
||||||
|
className="h-[46px] rounded-2xl border border-[var(--color-border)] bg-[var(--color-surface)] px-4 text-sm outline-none focus:border-[var(--color-accent)]"
|
||||||
|
>
|
||||||
|
<option value="">Тип получения</option>
|
||||||
|
<option value="delivery">Доставка</option>
|
||||||
|
<option value="pickup">Самовывоз</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
className="h-[46px] py-0"
|
className="h-[46px] py-0"
|
||||||
placeholder="Поиск по группе, клиенту или телефону"
|
placeholder="Поиск по группе, клиенту или телефону"
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,12 @@ const renderOrderNumbers = (group) => {
|
||||||
return `${visible.join(", ")} +${remaining}`;
|
return `${visible.join(", ")} +${remaining}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDeliveryTypeLabel = (deliveryType) => {
|
||||||
|
if (deliveryType === "pickup") return "Самовывоз";
|
||||||
|
if (deliveryType === "delivery") return "Доставка";
|
||||||
|
return "—";
|
||||||
|
};
|
||||||
|
|
||||||
const renderMobileOrderNumbers = (group) => {
|
const renderMobileOrderNumbers = (group) => {
|
||||||
const numbers = group.allBillNumbers || group.orderNumbers;
|
const numbers = group.allBillNumbers || group.orderNumbers;
|
||||||
if (!Array.isArray(numbers) || !numbers.length) {
|
if (!Array.isArray(numbers) || !numbers.length) {
|
||||||
|
|
@ -111,47 +117,56 @@ export const OrdersTable = ({
|
||||||
Группы не найдены. Попробуйте изменить поиск или статус.
|
Группы не найдены. Попробуйте изменить поиск или статус.
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{orderGroups.map((group) => (
|
{orderGroups.map((group) => {
|
||||||
<Button
|
const hasProblem = group.hasDeliveryProblem;
|
||||||
key={group.id}
|
const baseClass = "w-full rounded-[22px] border text-left transition";
|
||||||
variant="ghost"
|
const selectedClass = selectedOrderGroupId === group.id
|
||||||
onClick={() => onOpenOrder(group.id)}
|
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)]"
|
||||||
className={["w-full rounded-[22px] border text-left transition",
|
: hasProblem
|
||||||
selectedOrderGroupId === group.id ? "border-[var(--color-accent)] bg-[var(--color-accent-soft)]" : "border-[var(--color-border)] bg-[var(--color-surface-strong)]",
|
? "border-[var(--color-danger)] bg-[rgba(201,61,61,0.1)]"
|
||||||
].join(" ")}
|
: "border-[var(--color-border)] bg-[var(--color-surface-strong)]";
|
||||||
>
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-start gap-3">
|
|
||||||
<div className="min-w-0 truncate font-medium">
|
|
||||||
{group.displayTitle || group.customerName || group.groupKey}
|
|
||||||
</div>
|
|
||||||
<Badge className="self-start" tone={getOrderGroupStatusTone(group)}>
|
|
||||||
{getOrderGroupDisplayStatusLabel(group)}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
<div className="text-sm text-[var(--color-text-muted)]">
|
|
||||||
{group.displaySubtitle || [group.customerPhone, group.customerDate].filter(Boolean).join(" · ")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-3 text-sm text-[var(--color-text-muted)]">{buildGroupSummary(group)}</div>
|
return (
|
||||||
<div className="mt-2 text-sm text-[var(--color-text-muted)]">{renderMobileOrderNumbers(group)}</div>
|
<Button
|
||||||
{group.hasDeliveryProblem && (
|
key={group.id}
|
||||||
<div className="mt-2 rounded-lg border border-[var(--color-warning)] bg-[var(--color-warning-soft)] px-2 py-1 text-xs">
|
variant="ghost"
|
||||||
<span className="font-medium text-[var(--color-warning)]">⚠ Проблема с доставкой</span>
|
onClick={() => onOpenOrder(group.id)}
|
||||||
{group.deliveryProblemNote && (
|
className={[baseClass, selectedClass].join(" ")}
|
||||||
<div className="mt-0.5 text-[var(--color-text-muted)]">{group.deliveryProblemNote}</div>
|
>
|
||||||
)}
|
<div className="space-y-1">
|
||||||
|
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-start gap-3">
|
||||||
|
<div className="min-w-0 truncate font-medium">
|
||||||
|
{group.displayTitle || group.customerName || group.groupKey}
|
||||||
|
</div>
|
||||||
|
<Badge className="self-start" tone={getOrderGroupStatusTone(group)}>
|
||||||
|
{getOrderGroupDisplayStatusLabel(group)}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-[var(--color-text-muted)]">
|
||||||
|
{group.displaySubtitle || [group.customerPhone, group.customerDate].filter(Boolean).join(" · ")}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{!group.hasDeliveryProblem && Array.isArray(group?.driverShipmentData) && group.driverShipmentData.length > 0 && (
|
<div className="mt-3 text-sm text-[var(--color-text-muted)]">{buildGroupSummary(group)}</div>
|
||||||
<div className="mt-2 text-xs text-[var(--color-accent)]">✓ Доставка без проблем</div>
|
<div className="mt-2 text-sm text-[var(--color-text-muted)]">{renderMobileOrderNumbers(group)}</div>
|
||||||
)}
|
<div className="mt-2 text-sm text-[var(--color-text-muted)]">Тип: {getDeliveryTypeLabel(group.deliveryType)}</div>
|
||||||
<div className="mt-3 text-xs text-[var(--color-text-muted)]">
|
{group.hasDeliveryProblem && (
|
||||||
{formatDateTime(group.updatedAt)}
|
<div className="mt-2 rounded-lg border border-[var(--color-warning)] bg-[var(--color-warning-soft)] px-2 py-1 text-xs">
|
||||||
</div>
|
<span className="font-medium text-[var(--color-warning)]">⚠ Проблема с доставкой</span>
|
||||||
</Button>
|
{group.deliveryProblemNote && (
|
||||||
))}
|
<div className="mt-0.5 text-[var(--color-text-muted)]">{group.deliveryProblemNote}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!group.hasDeliveryProblem && Array.isArray(group?.driverShipmentData) && group.driverShipmentData.length > 0 && (
|
||||||
|
<div className="mt-2 text-xs text-[var(--color-accent)]">✓ Доставка без проблем</div>
|
||||||
|
)}
|
||||||
|
<div className="mt-3 text-xs text-[var(--color-text-muted)]">
|
||||||
|
{formatDateTime(group.updatedAt)}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="hidden overflow-x-auto md:block">
|
<div className="hidden overflow-x-auto md:block">
|
||||||
|
|
@ -167,16 +182,24 @@ export const OrdersTable = ({
|
||||||
<div className="px-5 py-4 font-medium">Статус</div>
|
<div className="px-5 py-4 font-medium">Статус</div>
|
||||||
<div className="px-5 py-4 font-medium">Водитель</div>
|
<div className="px-5 py-4 font-medium">Водитель</div>
|
||||||
<div className="px-5 py-4 font-medium">Дата доставки</div>
|
<div className="px-5 py-4 font-medium">Дата доставки</div>
|
||||||
<div className="px-5 py-4 font-medium">Проблема</div>
|
<div className="px-5 py-4 font-medium">Тип</div>
|
||||||
<div className="px-5 py-4 font-medium">Обновлён</div>
|
<div className="px-5 py-4 font-medium">Обновлён</div>
|
||||||
</div>
|
</div>
|
||||||
{orderGroups.map((group) => (
|
{orderGroups.map((group) => {
|
||||||
<Button
|
const hasProblem = group.hasDeliveryProblem;
|
||||||
key={group.id}
|
const rowClassName = `grid grid-cols-[minmax(180px,2.5fr)_minmax(100px,1.2fr)_minmax(100px,1fr)_minmax(80px,1fr)_minmax(120px,1.2fr)_minmax(80px,0.8fr)_minmax(130px,1.2fr)] gap-0 w-full border-t border-[var(--color-border)] text-left transition ${
|
||||||
variant="ghost"
|
hasProblem
|
||||||
className={`grid grid-cols-[minmax(180px,2.5fr)_minmax(100px,1.2fr)_minmax(100px,1fr)_minmax(80px,1fr)_minmax(120px,1.2fr)_minmax(80px,0.8fr)_minmax(130px,1.2fr)] gap-0 w-full border-t border-[var(--color-border)] text-left transition hover:bg-[var(--color-accent-soft)] ${selectedOrderGroupId === group.id ? "bg-[var(--color-accent-soft)]" : ""}`}
|
? "bg-[rgba(201,61,61,0.1)] hover:bg-[rgba(201,61,61,0.15)]"
|
||||||
onClick={() => onOpenOrder(group.id)}
|
: "hover:bg-[var(--color-accent-soft)]"
|
||||||
>
|
} ${selectedOrderGroupId === group.id ? "bg-[var(--color-accent-soft)]" : ""}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
key={group.id}
|
||||||
|
variant="ghost"
|
||||||
|
className={rowClassName}
|
||||||
|
onClick={() => onOpenOrder(group.id)}
|
||||||
|
>
|
||||||
<div className="px-5 py-4">
|
<div className="px-5 py-4">
|
||||||
<div className="font-medium">{group.displayTitle || group.customerName || group.groupKey}</div>
|
<div className="font-medium">{group.displayTitle || group.customerName || group.groupKey}</div>
|
||||||
<div className="mt-1 text-sm text-[var(--color-text-muted)]">
|
<div className="mt-1 text-sm text-[var(--color-text-muted)]">
|
||||||
|
|
@ -201,27 +224,15 @@ export const OrdersTable = ({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="px-5 py-4 text-sm">
|
<div className="px-5 py-4 text-sm">
|
||||||
{group.hasDeliveryProblem ? (
|
{getDeliveryTypeLabel(group.deliveryType)}
|
||||||
<div>
|
|
||||||
<span className="text-[var(--color-warning)] font-medium">⚠ Проблема</span>
|
|
||||||
{group.deliveryProblemNote && (
|
|
||||||
<div className="mt-0.5 text-xs text-[var(--color-text-muted)] line-clamp-2">
|
|
||||||
{group.deliveryProblemNote}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : Array.isArray(group?.driverShipmentData) && group.driverShipmentData.length > 0 ? (
|
|
||||||
<span className="text-[var(--color-accent)]">✓ Ок</span>
|
|
||||||
) : (
|
|
||||||
<span className="text-[var(--color-text-muted)]">—</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="px-5 py-4 text-sm text-[var(--color-text-muted)]">
|
<div className="px-5 py-4 text-sm text-[var(--color-text-muted)]">
|
||||||
{formatDateTime(group.updatedAt)}
|
{formatDateTime(group.updatedAt)}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
))}
|
);
|
||||||
</div>
|
})}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Panel>
|
</Panel>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ export const useOrderGroups = () => {
|
||||||
const [filters, setFilters] = React.useState({
|
const [filters, setFilters] = React.useState({
|
||||||
query: "",
|
query: "",
|
||||||
displayStatus: "all",
|
displayStatus: "all",
|
||||||
|
deliveryType: "",
|
||||||
});
|
});
|
||||||
const [selectedOrderGroupId, setSelectedOrderGroupId] = React.useState(null);
|
const [selectedOrderGroupId, setSelectedOrderGroupId] = React.useState(null);
|
||||||
const [isLoading, setIsLoading] = React.useState(true);
|
const [isLoading, setIsLoading] = React.useState(true);
|
||||||
|
|
|
||||||
|
|
@ -305,6 +305,11 @@ export const filterOrderGroups = (groups, filters = {}) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deliveryTypeFilter = (filters.deliveryType || "").trim().toLowerCase();
|
||||||
|
if (deliveryTypeFilter && group.deliveryType !== deliveryTypeFilter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!query) {
|
if (!query) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -333,8 +338,6 @@ export const ORDER_GROUP_DISPLAY_STATUS_OPTIONS = [
|
||||||
{ value: "delivery:paid_storage", label: DELIVERY_GROUP_STATUS_LABELS.paid_storage },
|
{ value: "delivery:paid_storage", label: DELIVERY_GROUP_STATUS_LABELS.paid_storage },
|
||||||
{ value: "delivery:cancelled", label: DELIVERY_GROUP_STATUS_LABELS.cancelled },
|
{ value: "delivery:cancelled", label: DELIVERY_GROUP_STATUS_LABELS.cancelled },
|
||||||
{ value: "status:manual_required", label: "Требует ручной обработки" },
|
{ value: "status:manual_required", label: "Требует ручной обработки" },
|
||||||
{ value: "status:second_sms_sent", label: "Повторное SMS" },
|
|
||||||
{ value: "status:sms_sent", label: "SMS отправлены" },
|
|
||||||
{ value: "status:ready_for_notification", label: "Готово к уведомлению" },
|
{ value: "status:ready_for_notification", label: "Готово к уведомлению" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue