supersam/src/components/orders/OrdersTable.jsx

186 lines
7.7 KiB
JavaScript

import { formatDateTime } from "../../utils/formatters";
import { Badge } from "../UI/Badge";
import { Panel } from "../UI/Panel";
import { OrderFilters } from "./OrderFilters";
import {
getOrderGroupDisplayStatusLabel,
getOrderGroupStatusTone,
} from "../../services/orderGroupViews";
const MAX_VISIBLE_INVOICES = 2;
const buildGroupSummary = (group) => {
const orderCountLabel = `${group.ordersCount || 0} ${group.ordersCount === 1 ? "заказ" : group.ordersCount < 5 ? "заказа" : "заказов"}`;
const parts = [orderCountLabel];
if (group.deliveryDate) {
const datePart = group.deliveryTime ? `${group.deliveryDate} · ${group.deliveryTime}` : group.deliveryDate;
parts.push(datePart);
}
if (group.assignedDriverName) {
parts.push(group.assignedDriverName);
}
return parts.join(" · ");
};
const renderOrderNumbers = (group) => {
const numbers = group.allBillNumbers || group.orderNumbers;
if (!Array.isArray(numbers) || !numbers.length) {
return "Номера не указаны";
}
if (numbers.length <= MAX_VISIBLE_INVOICES) {
return numbers.join(", ");
}
const visible = numbers.slice(0, MAX_VISIBLE_INVOICES);
const remaining = numbers.length - MAX_VISIBLE_INVOICES;
return `${visible.join(", ")} +${remaining}`;
};
const renderMobileOrderNumbers = (group) => {
const numbers = group.allBillNumbers || group.orderNumbers;
if (!Array.isArray(numbers) || !numbers.length) {
return "Номера не указаны";
}
if (numbers.length <= MAX_VISIBLE_INVOICES) {
return numbers.join(", ");
}
const visible = numbers.slice(0, MAX_VISIBLE_INVOICES);
const remaining = numbers.length - MAX_VISIBLE_INVOICES;
return (
<>
{visible.join(", ")}
<span className="ml-1 rounded-full bg-[var(--color-accent-soft)] px-1.5 py-0.5 text-xs font-medium text-[var(--color-accent)]">+{remaining}</span>
</>
);
};
export const OrdersTable = ({
orderGroups = [],
selectedOrderGroupId,
onOpenOrder,
filters,
setFilters,
statusOptions,
cities = [],
}) => {
return (
<Panel className="p-0">
<div className="space-y-4 border-b border-[var(--color-border)] px-5 py-4">
<div className="flex items-start justify-between gap-4">
<div>
<h2 className="text-lg font-semibold">Группы доставки</h2>
<p className="text-sm text-[var(--color-text-muted)]">
Поиск по группе, клиенту, телефону и дате доставки.
</p>
</div>
<Badge tone="neutral">{orderGroups.length}</Badge>
</div>
{filters && setFilters ? (
<OrderFilters filters={filters} setFilters={setFilters} statusOptions={statusOptions} cities={cities} />
) : null}
</div>
<div className="space-y-3 p-4 md:hidden">
{!orderGroups.length ? (
<div className="rounded-[28px] border border-dashed border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 text-sm text-[var(--color-text-muted)]">
Группы не найдены. Попробуйте изменить поиск или статус.
</div>
) : null}
{orderGroups.map((group) => (
<button
key={group.id}
type="button"
onClick={() => onOpenOrder(group.id)}
className={[
"w-full rounded-[22px] border border-[var(--color-border)] bg-[var(--color-surface-strong)] p-4 text-left transition",
selectedOrderGroupId === group.id ? "bg-[var(--color-accent-soft)]" : "",
].join(" ")}
>
<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>
<div className="mt-2 text-sm text-[var(--color-text-muted)]">{renderMobileOrderNumbers(group)}</div>
<div className="mt-3 text-xs text-[var(--color-text-muted)]">
{formatDateTime(group.updatedAt)}
</div>
</button>
))}
</div>
<div className="hidden overflow-x-auto md:block">
{!orderGroups.length ? (
<div className="px-5 py-6 text-sm text-[var(--color-text-muted)]">
Группы не найдены. Попробуйте изменить поиск или статус.
</div>
) : (
<table className="min-w-full border-collapse">
<thead className="bg-[var(--color-surface-strong)] text-left text-xs uppercase tracking-[0.16em] text-[var(--color-text-muted)]">
<tr>
<th className="px-5 py-4 font-medium">Группа / Клиент</th>
<th className="px-5 py-4 font-medium">Счёта</th>
<th className="px-5 py-4 font-medium">Статус</th>
<th className="px-5 py-4 font-medium">Водитель</th>
<th className="px-5 py-4 font-medium">Дата доставки</th>
<th className="px-5 py-4 font-medium">Обновлён</th>
</tr>
</thead>
<tbody>
{orderGroups.map((group) => (
<tr
key={group.id}
className={[
"cursor-pointer border-t border-[var(--color-border)] transition hover:bg-[var(--color-accent-soft)]",
selectedOrderGroupId === group.id ? "bg-[var(--color-accent-soft)]" : "",
].join(" ")}
onClick={() => onOpenOrder(group.id)}
>
<td className="px-5 py-4">
<div className="font-medium">{group.displayTitle || group.customerName || group.groupKey}</div>
<div className="mt-1 text-sm text-[var(--color-text-muted)]">
{[group.customerName, group.customerPhone].filter(Boolean).join(" · ")}
</div>
<div className="text-xs text-[var(--color-text-muted)]">{group.groupKey}</div>
</td>
<td className="max-w-[260px] px-5 py-4 text-sm text-[var(--color-text-muted)]">
{renderOrderNumbers(group)}
</td>
<td className="px-5 py-4">
<Badge tone={getOrderGroupStatusTone(group)}>{getOrderGroupDisplayStatusLabel(group)}</Badge>
</td>
<td className="px-5 py-4 text-sm">
{group.assignedDriverName || <span className="text-[var(--color-text-muted)]"></span>}
</td>
<td className="px-5 py-4 text-sm">
{group.deliveryDate ? (
<span>{group.deliveryDate}{group.deliveryTime ? <span className="text-[var(--color-text-muted)]"> · {group.deliveryTime}</span> : ""}</span>
) : (
<span className="text-[var(--color-text-muted)]"></span>
)}
</td>
<td className="px-5 py-4 text-sm text-[var(--color-text-muted)]">
{formatDateTime(group.updatedAt)}
</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</Panel>
);
};