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:
root 2026-06-18 15:00:58 +00:00
parent 8d3d8dc0fe
commit 54386e9710
4 changed files with 90 additions and 65 deletions

View File

@ -105,6 +105,16 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities =
</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
className="h-[46px] py-0"
placeholder="Поиск по группе, клиенту или телефону"

View File

@ -54,6 +54,12 @@ const renderOrderNumbers = (group) => {
return `${visible.join(", ")} +${remaining}`;
};
const getDeliveryTypeLabel = (deliveryType) => {
if (deliveryType === "pickup") return "Самовывоз";
if (deliveryType === "delivery") return "Доставка";
return "—";
};
const renderMobileOrderNumbers = (group) => {
const numbers = group.allBillNumbers || group.orderNumbers;
if (!Array.isArray(numbers) || !numbers.length) {
@ -111,14 +117,21 @@ export const OrdersTable = ({
Группы не найдены. Попробуйте изменить поиск или статус.
</div>
) : null}
{orderGroups.map((group) => (
{orderGroups.map((group) => {
const hasProblem = group.hasDeliveryProblem;
const baseClass = "w-full rounded-[22px] border text-left transition";
const selectedClass = selectedOrderGroupId === group.id
? "border-[var(--color-accent)] bg-[var(--color-accent-soft)]"
: hasProblem
? "border-[var(--color-danger)] bg-[rgba(201,61,61,0.1)]"
: "border-[var(--color-border)] bg-[var(--color-surface-strong)]";
return (
<Button
key={group.id}
variant="ghost"
onClick={() => onOpenOrder(group.id)}
className={["w-full rounded-[22px] border text-left transition",
selectedOrderGroupId === group.id ? "border-[var(--color-accent)] bg-[var(--color-accent-soft)]" : "border-[var(--color-border)] bg-[var(--color-surface-strong)]",
].join(" ")}
className={[baseClass, selectedClass].join(" ")}
>
<div className="space-y-1">
<div className="grid grid-cols-[minmax(0,1fr)_auto] items-start gap-3">
@ -136,6 +149,7 @@ export const OrdersTable = ({
<div className="mt-3 text-sm text-[var(--color-text-muted)]">{buildGroupSummary(group)}</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>
{group.hasDeliveryProblem && (
<div className="mt-2 rounded-lg border border-[var(--color-warning)] bg-[var(--color-warning-soft)] px-2 py-1 text-xs">
<span className="font-medium text-[var(--color-warning)]"> Проблема с доставкой</span>
@ -151,7 +165,8 @@ export const OrdersTable = ({
{formatDateTime(group.updatedAt)}
</div>
</Button>
))}
);
})}
</div>
<div className="hidden overflow-x-auto md:block">
@ -167,14 +182,22 @@ 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>
{orderGroups.map((group) => (
{orderGroups.map((group) => {
const hasProblem = group.hasDeliveryProblem;
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 ${
hasProblem
? "bg-[rgba(201,61,61,0.1)] hover:bg-[rgba(201,61,61,0.15)]"
: "hover:bg-[var(--color-accent-soft)]"
} ${selectedOrderGroupId === group.id ? "bg-[var(--color-accent-soft)]" : ""}`;
return (
<Button
key={group.id}
variant="ghost"
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)]" : ""}`}
className={rowClassName}
onClick={() => onOpenOrder(group.id)}
>
<div className="px-5 py-4">
@ -201,26 +224,14 @@ export const OrdersTable = ({
)}
</div>
<div className="px-5 py-4 text-sm">
{group.hasDeliveryProblem ? (
<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>
)}
{getDeliveryTypeLabel(group.deliveryType)}
</div>
<div className="px-5 py-4 text-sm text-[var(--color-text-muted)]">
{formatDateTime(group.updatedAt)}
</div>
</Button>
))}
);
})}
</div>
)}
</div>

View File

@ -14,6 +14,7 @@ export const useOrderGroups = () => {
const [filters, setFilters] = React.useState({
query: "",
displayStatus: "all",
deliveryType: "",
});
const [selectedOrderGroupId, setSelectedOrderGroupId] = React.useState(null);
const [isLoading, setIsLoading] = React.useState(true);

View File

@ -305,6 +305,11 @@ export const filterOrderGroups = (groups, filters = {}) => {
}
}
const deliveryTypeFilter = (filters.deliveryType || "").trim().toLowerCase();
if (deliveryTypeFilter && group.deliveryType !== deliveryTypeFilter) {
return false;
}
if (!query) {
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:cancelled", label: DELIVERY_GROUP_STATUS_LABELS.cancelled },
{ value: "status:manual_required", label: "Требует ручной обработки" },
{ value: "status:second_sms_sent", label: "Повторное SMS" },
{ value: "status:sms_sent", label: "SMS отправлены" },
{ value: "status:ready_for_notification", label: "Готово к уведомлению" },
];