fix: unify table styles — no rounded rows, wider type col, horizontal scroll, pagination by filtered groups

This commit is contained in:
root 2026-06-30 11:43:15 +00:00
parent c0c549adca
commit 581239cc73
2 changed files with 70 additions and 72 deletions

View File

@ -7,7 +7,6 @@ import {
ORDER_GROUP_DISPLAY_STATUS_OPTIONS,
} from "../../services/orderGroupViews";
import { Badge } from "../UI/Badge";
import { Button } from "../UI/Button";
import { Panel } from "../UI/Panel";
import { SkeletonPage } from "../UI/Loading";
import { Pagination } from "../UI/Pagination";
@ -33,12 +32,11 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
[filters, orderGroups],
);
// Paginate
const totalPages = Math.ceil(filteredGroups.length / PAGE_SIZE);
const paginatedGroups = React.useMemo(() => {
const start = (page - 1) * PAGE_SIZE;
return filteredGroups.slice(start, start + PAGE_SIZE);
}, [filteredGroups, page]);
const totalPages = Math.ceil(filteredGroups.length / PAGE_SIZE);
const statusGroups = React.useMemo(() => {
const map = new Map();
@ -71,17 +69,18 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
const totalGroups = filteredGroups.length;
const COLS = "grid-cols-[minmax(140px,2fr)_minmax(80px,1fr)_minmax(70px,0.7fr)_minmax(100px,1.2fr)_minmax(80px,1fr)_minmax(100px,1fr)_minmax(120px,1fr)]";
// Same column layout as OrdersTable + Тип
const COLS = "grid-cols-[minmax(130px,2fr)_minmax(80px,1fr)_minmax(100px,0.8fr)_minmax(100px,1fr)_minmax(100px,1fr)_minmax(90px,0.8fr)_minmax(110px,1fr)]";
const TableHeader = () => (
<div className={`grid ${COLS} gap-0 border-b border-[var(--color-border)] bg-[var(--color-surface-strong)] text-xs uppercase tracking-[0.14em] text-[var(--color-text-muted)]`}>
<div className="px-4 py-3 font-medium">Клиент</div>
<div className="px-4 py-3 font-medium">Город</div>
<div className="px-4 py-3 font-medium">Тип</div>
<div className="px-4 py-3 font-medium">Дата доставки</div>
<div className="px-4 py-3 font-medium">Водитель</div>
<div className="px-4 py-3 font-medium">Статус</div>
<div className="px-4 py-3 font-medium">Обновлён</div>
<div className={`grid ${COLS} gap-0 border-b border-[var(--color-border)] bg-[var(--color-surface-strong)] text-[10px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]`}>
<div className="px-3 py-2.5">Клиент</div>
<div className="px-3 py-2.5">Город</div>
<div className="px-3 py-2.5">Тип</div>
<div className="px-3 py-2.5">Дата доставки</div>
<div className="px-3 py-2.5">Водитель</div>
<div className="px-3 py-2.5">Статус</div>
<div className="px-3 py-2.5">Обновлён</div>
</div>
);
@ -112,7 +111,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
По этому поиску ничего не найдено.
</div>
) : (
<div className="grid gap-4">
<div className="grid gap-0">
{Array.from(statusGroups.entries()).sort(([a], [b]) => {
const idxA = FUNNEL_ORDER.indexOf(a);
const idxB = FUNNEL_ORDER.indexOf(b);
@ -124,10 +123,10 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
const isCollapsed = collapsedSections.has(statusValue);
return (
<Panel key={statusValue} className="overflow-hidden p-0">
<Button
variant="ghost"
className="flex w-full items-center justify-between px-5 py-3 text-left"
<div key={statusValue} className="border-b border-[var(--color-border)] last:border-b-0">
{/* Section header — same style as OrdersTable */}
<button
type="button"
onClick={() => {
setCollapsedSections((prev) => {
const next = new Set(prev);
@ -139,65 +138,65 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
return next;
});
}}
className="flex w-full items-center gap-3 px-4 py-2.5 text-left transition hover:bg-[var(--color-accent-soft)]"
>
<div className="flex items-center gap-2">
<h3 className="font-semibold">{label}</h3>
<Badge tone={groups.length > 0 ? "neutral" : "muted"}>{groups.length}</Badge>
</div>
<svg
className="h-4 w-4 text-[var(--color-text-muted)] transition-transform"
style={{ transform: isCollapsed ? "rotate(-90deg)" : "rotate(0deg)" }}
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
className="h-4 w-4 shrink-0 text-[var(--color-text-muted)] transition-transform"
style={{ transform: isCollapsed ? "rotate(0deg)" : "rotate(90deg)" }}
fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}
>
<path strokeLinecap="round" strokeLinejoin="round" d="M19 9l-7 7-7-7" />
<path strokeLinecap="round" strokeLinejoin="round" d="M9 5l7 7-7 7" />
</svg>
</Button>
<Badge tone={groups.length > 0 ? "neutral" : "muted"}>{label}</Badge>
<span className="text-xs text-[var(--color-text-muted)]">{groups.length}</span>
</button>
{!isCollapsed && (
<div className="overflow-x-auto">
{/* Horizontal scroll wrapper — min-w so columns don't squish */}
<div className="min-w-[960px]">
<TableHeader />
{groups.map((group) => (
<Button
<button
key={group.id}
variant="ghost"
className={`grid ${COLS} gap-0 w-full border-t border-[var(--color-border)] text-left`}
type="button"
className={`grid ${COLS} gap-0 w-full border-t border-[var(--color-border)] text-left transition hover:bg-[var(--color-accent-soft)]`}
onClick={() => { if (onSelectSet) onSelectSet(group.id); }}
>
<div className="px-4 py-2.5">
<div className="font-medium">{group.displayTitle || group.customerName || group.groupKey}</div>
<div className="text-xs text-[var(--color-text-muted)]">{group.customerPhone || "—"}</div>
<div className="min-w-0 px-3 py-1.5">
<div className="text-xs font-medium leading-snug break-words" style={{display:"-webkit-box",WebkitLineClamp:2,WebkitBoxOrient:"vertical",overflow:"hidden"}}>{group.displayTitle || group.customerName || group.groupKey}</div>
<div className="mt-0.5 text-[11px] text-[var(--color-text-muted)]">{group.customerPhone || "—"}</div>
</div>
<div className="px-4 py-2.5 text-sm">
{group.city || group.customerAddress || "—"}
<div className="px-3 py-1.5 text-xs text-[var(--color-text-muted)]">
{group.city || "—"}
</div>
<div className="px-4 py-2.5">
<span className={`inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-semibold ${group.deliveryType === "pickup" ? "bg-[var(--color-accent-soft)] text-[var(--color-accent)]" : "bg-[var(--color-surface-strong)] text-[var(--color-text-muted)]"}`}>
{group.deliveryType === "pickup" ? "🏪 Самовывоз" : "🚚 Доставка"}
<div className="px-3 py-1.5 text-xs">
<span className="inline-flex items-center gap-1">
{group.deliveryType === "pickup" ? "🏪" : "🚚"}
<span className="text-[var(--color-text-muted)]">{group.deliveryType === "pickup" ? "Самовывоз" : "Доставка"}</span>
</span>
</div>
<div className="px-4 py-2.5 text-sm">
<div className="px-3 py-1.5 text-xs">
{group.deliveryDate
? <span>{formatDate(group.deliveryDate)}{group.deliveryTime ? <span className="text-[var(--color-text-muted)]"> · {group.deliveryTime}</span> : ""}</span>
: <span className="text-[var(--color-text-muted)]"></span>
}
</div>
<div className="px-4 py-2.5 text-sm">
<div className="px-3 py-1.5 text-xs">
{group.assignedDriverName || <span className="text-[var(--color-text-muted)]"></span>}
</div>
<div className="px-4 py-2.5">
<div className="px-3 py-1.5 flex items-center">
<Badge tone={getOrderGroupStatusTone(group)}>{getOrderGroupDisplayStatusLabel(group)}</Badge>
</div>
<div className="px-4 py-2.5 text-sm text-[var(--color-text-muted)]">
<div className="px-3 py-1.5 text-xs text-[var(--color-text-muted)]">
{formatDateTime(group.updatedAt)}
</div>
</Button>
</button>
))}
</div>
</div>
)}
</Panel>
</div>
);
})}
</div>

View File

@ -92,7 +92,7 @@ const StatusSection = ({ statusKey, groups, isOpen, onToggle, selectedOrderGroup
{groups.map((group) => {
const hasProblem = group.hasDeliveryProblem;
const isAgreedRow = isAgreedDelivery(group);
const rowClassName = `grid grid-cols-[minmax(130px,2fr)_minmax(90px,1fr)_minmax(80px,0.8fr)_minmax(80px,1fr)_minmax(100px,1fr)_minmax(70px,0.7fr)_minmax(80px,0.8fr)] gap-0 w-full border-t text-left transition ${
const rowClassName = `grid grid-cols-[minmax(130px,2fr)_minmax(90px,1fr)_minmax(80px,0.8fr)_minmax(80px,1fr)_minmax(100px,1fr)_minmax(100px,0.8fr)_minmax(80px,0.8fr)] gap-0 w-full border-t text-left transition ${
isAgreedRow
? "border-[rgba(18,128,92,0.25)] bg-[rgba(18,128,92,0.07)] hover:bg-[rgba(18,128,92,0.12)]"
: hasProblem
@ -265,14 +265,13 @@ export const OrdersTable = ({
const [page, setPage] = useState(1);
const PAGE_SIZE = 30;
// Paginate groups before grouping
// Paginate filtered groups (after filter, before grouping)
const totalPages = Math.ceil(orderGroups.length / PAGE_SIZE);
const paginatedGroups = useMemo(() => {
const start = (page - 1) * PAGE_SIZE;
return orderGroups.slice(start, start + PAGE_SIZE);
}, [orderGroups, page]);
const totalPages = Math.ceil(orderGroups.length / PAGE_SIZE);
// Group by status
const grouped = useMemo(() => {
const map = {};
@ -354,7 +353,7 @@ export const OrdersTable = ({
</div>
) : (
<div className="overflow-x-auto">
<div className="min-w-[920px]">
<div className="min-w-[1000px]">
{grouped.map(({ key, groups }) => (
<StatusSection
key={key}