fix: remove Группы tab from logistician, remove Справка, Телефон→Город, Заказы→Водитель, add city filter
This commit is contained in:
parent
bb439a4d93
commit
6f29948f8a
|
|
@ -12,9 +12,17 @@ import { OrderFilters } from "../orders/OrderFilters";
|
|||
import { formatDateTime } from "../../utils/formatters";
|
||||
|
||||
export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusOptions = ORDER_GROUP_DISPLAY_STATUS_OPTIONS }) => {
|
||||
const [filters, setFilters] = React.useState({ query: "", displayStatus: "all" });
|
||||
const [filters, setFilters] = React.useState({ query: "", displayStatus: "all", city: "" });
|
||||
const [collapsedSections, setCollapsedSections] = React.useState(new Set());
|
||||
|
||||
const cities = React.useMemo(() => {
|
||||
const set = new Set();
|
||||
for (const g of orderGroups) {
|
||||
if (g.city) set.add(g.city);
|
||||
}
|
||||
return [...set].sort();
|
||||
}, [orderGroups]);
|
||||
|
||||
const filteredGroups = React.useMemo(
|
||||
() => filterOrderGroups(orderGroups, filters),
|
||||
[filters, orderGroups],
|
||||
|
|
@ -55,9 +63,9 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
|
|||
<thead className="bg-[var(--color-surface-strong)] text-left text-xs uppercase tracking-[0.14em] text-[var(--color-text-muted)]">
|
||||
<tr>
|
||||
<th className="px-4 py-3 font-medium">Клиент</th>
|
||||
<th className="px-4 py-3 font-medium hidden sm:table-cell">Телефон</th>
|
||||
<th className="px-4 py-3 font-medium hidden sm:table-cell">Город</th>
|
||||
<th className="px-4 py-3 font-medium hidden md:table-cell">Дата доставки</th>
|
||||
<th className="px-4 py-3 font-medium hidden lg:table-cell">Заказы</th>
|
||||
<th className="px-4 py-3 font-medium hidden lg:table-cell">Водитель</th>
|
||||
<th className="px-4 py-3 font-medium">Статус</th>
|
||||
<th className="px-4 py-3 font-medium hidden md:table-cell">Обновлён</th>
|
||||
</tr>
|
||||
|
|
@ -78,6 +86,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
|
|||
filters={filters}
|
||||
setFilters={setFilters}
|
||||
statusOptions={statusOptions}
|
||||
cities={cities}
|
||||
/>
|
||||
</Panel>
|
||||
|
||||
|
|
@ -147,7 +156,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
|
|||
<div className="text-xs text-[var(--color-text-muted)] md:hidden">{group.deliveryDate || "—"}</div>
|
||||
</td>
|
||||
<td className="px-4 py-2.5 text-sm hidden sm:table-cell">
|
||||
{group.customerPhone || "—"}
|
||||
{group.city || group.customerAddress || "—"}
|
||||
</td>
|
||||
<td className="px-4 py-2.5 text-sm hidden md:table-cell">
|
||||
{group.deliveryDate
|
||||
|
|
@ -156,7 +165,7 @@ export const LogisticsReadinessBoard = ({ orderGroups = [], onSelectSet, statusO
|
|||
}
|
||||
</td>
|
||||
<td className="px-4 py-2.5 text-sm hidden lg:table-cell">
|
||||
{group.ordersCount || 0} {group.ordersCount === 1 ? "заказ" : group.ordersCount < 5 ? "заказа" : "заказов"}
|
||||
{group.assignedDriverName || <span className="text-[var(--color-text-muted)]">—</span>}
|
||||
</td>
|
||||
<td className="px-4 py-2.5">
|
||||
<Badge tone={getOrderGroupStatusTone(group)}>{getOrderGroupDisplayStatusLabel(group)}</Badge>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { Input } from "../UI/Input";
|
|||
import { Panel } from "../UI/Panel";
|
||||
import { DatePicker } from "../UI/DatePicker";
|
||||
|
||||
export const OrderFilters = ({ filters, setFilters, statusOptions = [] }) => {
|
||||
export const OrderFilters = ({ filters, setFilters, statusOptions = [], cities = [] }) => {
|
||||
const statusValue = filters.displayStatus || filters.status || "all";
|
||||
const selectedStatusLabel = statusOptions.find((option) => option.value === statusValue)?.label || statusValue;
|
||||
const [isStatusOpen, setIsStatusOpen] = React.useState(false);
|
||||
|
|
@ -38,8 +38,8 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [] }) => {
|
|||
return (
|
||||
<Panel className="p-4">
|
||||
<div className="flex flex-col gap-3">
|
||||
{/* Row 1: Status + Search */}
|
||||
<div className="grid gap-3 md:grid-cols-[minmax(12rem,0.7fr)_minmax(0,1.6fr)] md:items-end">
|
||||
{/* Row 1: Status + City + Search */}
|
||||
<div className="grid gap-3 md:grid-cols-[minmax(12rem,0.7fr)_minmax(0,0.5fr)_minmax(0,1.6fr)] md:items-end">
|
||||
<div ref={statusMenuRef} className="relative flex min-w-0 flex-col gap-2">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -93,6 +93,19 @@ export const OrderFilters = ({ filters, setFilters, statusOptions = [] }) => {
|
|||
) : null}
|
||||
</div>
|
||||
|
||||
{cities.length > 0 && (
|
||||
<select
|
||||
value={filters.city || ""}
|
||||
onChange={(e) => updateFilter("city", 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>
|
||||
{cities.map((c) => (
|
||||
<option key={c} value={c}>{c}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
|
||||
<Input
|
||||
className="h-[46px] py-0"
|
||||
placeholder="Поиск по группе, клиенту или телефону"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import ErrorLogPanel from "../components/admin/ErrorLogPanel";
|
|||
import { StopWordsPanel } from "../components/admin/StopWordsPanel";
|
||||
import { ActionLogPanel } from "../components/admin/ActionLogPanel";
|
||||
import { Panel } from "../components/UI/Panel";
|
||||
import { ProductGuidePanel } from "../components/dashboard/ProductGuidePanel";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { useNotifications } from "../hooks/useNotifications";
|
||||
import { usePushNotifications } from "../hooks/usePushNotifications";
|
||||
|
|
@ -102,22 +101,20 @@ export const DashboardPage = () => {
|
|||
: userRole === "logistician"
|
||||
? [
|
||||
{ key: "logistics", label: "Логистика", description: "Группы доставки по готовности к уведомлению.", badge: String(allOrderGroups.length || orderGroups.length || 0) },
|
||||
{ key: "orders", label: "Группы", description: "Реестр групп доставки.", badge: null },
|
||||
]
|
||||
: [
|
||||
{ key: section.key, label: section.label, description: section.description, badge: String(allOrderGroups.length || orderGroups.length || 0) },
|
||||
];
|
||||
|
||||
const guideSectionMeta = { key: "guide", label: "Справка", description: "Карта продукта, роли, сценарии и частые вопросы." };
|
||||
const activeSectionMeta = activeSection === "guide" ? guideSectionMeta : navItems.find((n) => n.key === activeSection) || navItems[0];
|
||||
const isGuideOpen = activeSection === "guide";
|
||||
const activeSectionMeta = navItems.find((n) => n.key === activeSection) || navItems[0];
|
||||
const isGuideOpen = false;
|
||||
|
||||
if (!user) {
|
||||
return <Navigate to="/login" replace />;
|
||||
}
|
||||
|
||||
const renderActiveSection = () => {
|
||||
if (activeSection === "guide") return <ProductGuidePanel />;
|
||||
|
||||
if (activeSection === "analytics") return <div className="space-y-6 xl:space-y-8"><AdminDashboard /></div>;
|
||||
if (activeSection === "users") return <div className="space-y-6 xl:space-y-8"><UserManagementPanel /></div>;
|
||||
if (activeSection === "stop_words") return <div className="space-y-6 xl:space-y-8"><StopWordsPanel /></div>;
|
||||
|
|
@ -159,8 +156,8 @@ export const DashboardPage = () => {
|
|||
onInstallApp={onInstallApp}
|
||||
isInstalled={isInstalled}
|
||||
isInstallAvailable={isInstallAvailable}
|
||||
onOpenGuide={() => setActiveSection("guide")}
|
||||
isGuideOpen={isGuideOpen}
|
||||
onOpenGuide={undefined}
|
||||
isGuideOpen={false}
|
||||
navItems={navItems}
|
||||
activeSection={activeSection}
|
||||
onSectionChange={setActiveSection}
|
||||
|
|
|
|||
|
|
@ -251,6 +251,8 @@ export const filterOrderGroups = (groups, filters = {}) => {
|
|||
getOrderGroupStatusLabel(group.status),
|
||||
group.deliveryStatus,
|
||||
getOrderGroupDeliveryStatusLabel(group.deliveryStatus),
|
||||
group.city,
|
||||
group.customerAddress,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" "))
|
||||
|
|
@ -289,6 +291,14 @@ export const filterOrderGroups = (groups, filters = {}) => {
|
|||
}
|
||||
}
|
||||
|
||||
const cityFilter = (filters.city || "").trim().toLowerCase();
|
||||
if (cityFilter) {
|
||||
const groupCity = (group.city || "").toLowerCase();
|
||||
if (!groupCity.includes(cityFilter) && cityFilter !== groupCity) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!query) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,15 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
};
|
||||
|
||||
const deliveryAddress = normalizeText(row.delivery_address) || extractAddressFromSourceOrders(row.source_orders);
|
||||
const customerAddress = normalizeText(row.customer_address) || "";
|
||||
const extractCity = (addr) => {
|
||||
if (!addr) return "";
|
||||
const m = addr.match(/(?:г\.|гор\.?|пос\.|с\.|село|дер\.|пгт|город)\s*([А-ЯЁа-яёA-Za-z\-\s]+?)(?:[,\\s]|$)/i);
|
||||
if (m) return m[1].trim();
|
||||
const words = addr.split(/[,.]/)[0].trim();
|
||||
return words;
|
||||
};
|
||||
const city = extractCity(customerAddress) || extractCity(deliveryAddress) || "";
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
|
|
@ -107,6 +116,8 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
customerPhoneNormalized: parsedKey.phone || normalizePhone(customerPhone),
|
||||
customerDate,
|
||||
deliveryAddress,
|
||||
customerAddress,
|
||||
city,
|
||||
assignedDriverId: row.assigned_driver_id || null,
|
||||
assignedDriverName: row.assigned_driver?.name || "",
|
||||
ordersCount,
|
||||
|
|
@ -154,6 +165,8 @@ export const mapOrderGroupRowToDeliveryGroup = (row) => {
|
|||
customerPhone,
|
||||
customerDate,
|
||||
deliveryAddress,
|
||||
customerAddress,
|
||||
city,
|
||||
rawDeliveryHalfDay,
|
||||
rawDeliveryTime,
|
||||
row.delivery_window,
|
||||
|
|
@ -203,7 +216,7 @@ export const updateOrderGroupDeliveryChoice = async ({
|
|||
|
||||
const { data, error } = await client
|
||||
.from("order_groups")
|
||||
.select("id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)")
|
||||
.select("id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, customer_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)")
|
||||
.eq("id", orderGroupId)
|
||||
.single();
|
||||
|
||||
|
|
@ -357,7 +370,7 @@ export const fetchOrderGroups = async () => {
|
|||
const client = requireSupabase();
|
||||
const { data, error } = await client
|
||||
.from("order_groups")
|
||||
.select("id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)")
|
||||
.select("id, group_key, order_numbers, status, delivery_status, sms_sent_at, created_at, updated_at, created_from_exchange_at, source_key, customer_name, customer_phone, customer_phone_normalized, customer_date, orders_total, orders_ready, orders_not_ready, source_orders, order_list, order_list_structured, delivery_invitation_id, delivery_link, notification_status, sms_attempts, first_sms_sent_at, second_sms_sent_at, last_sms_error, next_notification_check_at, delivery_date, delivery_time, delivery_address, customer_address, delivery_date_source, manual_confirmation_at, paid_storage_at, assigned_driver_id, assigned_driver:users!order_groups_assigned_driver_id_fkey(id, name)")
|
||||
.order("updated_at", { ascending: false });
|
||||
|
||||
if (error) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue