Обновлен компонент SuppliesDashboard: изменены активные вкладки на "Все" и "Товары", добавлены новые компоненты для отображения всех поставок и товаров. Оптимизирована логика отображения уведомлений о количестве ожидающих заказов. В компоненте RealSupplyOrdersTab добавлены функции фильтрации и сортировки заказов, улучшен интерфейс для отображения статистики и информации о заказах.
This commit is contained in:
@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { FulfillmentGoodsTab } from "./fulfillment-goods-tab";
|
||||
import { RealSupplyOrdersTab } from "./real-supply-orders-tab";
|
||||
import { SellerSupplyOrdersTab } from "./seller-supply-orders-tab";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
|
||||
interface AllSuppliesTabProps {
|
||||
pendingSupplyOrders?: number;
|
||||
}
|
||||
|
||||
export function AllSuppliesTab({
|
||||
pendingSupplyOrders = 0,
|
||||
}: AllSuppliesTabProps) {
|
||||
const { user } = useAuth();
|
||||
|
||||
// Определяем тип организации для выбора правильного компонента
|
||||
const isWholesale = user?.organization?.type === "WHOLESALE";
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-hidden space-y-4">
|
||||
{/* Секция товаров */}
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<h3 className="text-white font-semibold text-lg mb-3">Товары</h3>
|
||||
<div className="h-64 overflow-hidden">
|
||||
<FulfillmentGoodsTab />
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Секция расходников */}
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<h3 className="text-white font-semibold text-lg mb-3">Расходники</h3>
|
||||
<div className="h-64 overflow-hidden">
|
||||
{isWholesale ? <RealSupplyOrdersTab /> : <SellerSupplyOrdersTab />}
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -5,7 +5,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { FulfillmentGoodsTab } from "./fulfillment-goods-tab";
|
||||
import { RealSupplyOrdersTab } from "./real-supply-orders-tab";
|
||||
import { SellerSupplyOrdersTab } from "./seller-supply-orders-tab";
|
||||
import { PvzReturnsTab } from "./pvz-returns-tab";
|
||||
import { AllSuppliesTab } from "./all-supplies-tab";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
|
||||
interface FulfillmentSuppliesTabProps {
|
||||
@ -17,7 +17,7 @@ export function FulfillmentSuppliesTab({
|
||||
defaultSubTab,
|
||||
pendingSupplyOrders = 0,
|
||||
}: FulfillmentSuppliesTabProps) {
|
||||
const [activeSubTab, setActiveSubTab] = useState("goods");
|
||||
const [activeSubTab, setActiveSubTab] = useState("all");
|
||||
const { user } = useAuth();
|
||||
|
||||
// Устанавливаем активную подвкладку при получении defaultSubTab
|
||||
@ -39,6 +39,12 @@ export function FulfillmentSuppliesTab({
|
||||
>
|
||||
{/* Подвкладки для ФФ */}
|
||||
<TabsList className="grid grid-cols-3 bg-white/5 backdrop-blur border-white/10 mb-2 w-fit text-sm">
|
||||
<TabsTrigger
|
||||
value="all"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 px-3 sm:px-4"
|
||||
>
|
||||
Все
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="goods"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 px-3 sm:px-4"
|
||||
@ -47,7 +53,9 @@ export function FulfillmentSuppliesTab({
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="supplies"
|
||||
className={`data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 px-3 sm:px-4 relative ${pendingSupplyOrders > 0 ? 'animate-pulse' : ''}`}
|
||||
className={`data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 px-3 sm:px-4 relative ${
|
||||
pendingSupplyOrders > 0 ? "animate-pulse" : ""
|
||||
}`}
|
||||
>
|
||||
Расходники
|
||||
{pendingSupplyOrders > 0 && (
|
||||
@ -56,15 +64,12 @@ export function FulfillmentSuppliesTab({
|
||||
</div>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="returns"
|
||||
className="data-[state=active]:bg-white/20 data-[state=active]:text-white text-white/60 px-3 sm:px-4"
|
||||
>
|
||||
<span className="hidden sm:inline">Возвраты с ПВЗ</span>
|
||||
<span className="sm:hidden">Возвраты</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="all" className="mt-0 flex-1 overflow-hidden">
|
||||
<AllSuppliesTab pendingSupplyOrders={pendingSupplyOrders} />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="goods" className="mt-0 flex-1 overflow-hidden">
|
||||
<FulfillmentGoodsTab />
|
||||
</TabsContent>
|
||||
@ -72,10 +77,6 @@ export function FulfillmentSuppliesTab({
|
||||
<TabsContent value="supplies" className="mt-0 flex-1 overflow-hidden">
|
||||
{isWholesale ? <RealSupplyOrdersTab /> : <SellerSupplyOrdersTab />}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="returns" className="mt-0 flex-1 overflow-hidden">
|
||||
<PvzReturnsTab />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,32 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { StatsCard } from "../ui/stats-card";
|
||||
import { StatsGrid } from "../ui/stats-grid";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||
import { toast } from "sonner";
|
||||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||||
import { UPDATE_SUPPLY_ORDER_STATUS } from "@/graphql/mutations";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
ChevronRight,
|
||||
ChevronDown,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Truck,
|
||||
Calendar,
|
||||
Building2,
|
||||
TrendingUp,
|
||||
User,
|
||||
DollarSign,
|
||||
Wrench,
|
||||
Package2,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
User,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
Clock,
|
||||
Truck,
|
||||
TrendingUp,
|
||||
TrendingDown,
|
||||
Search,
|
||||
Store,
|
||||
ArrowUpDown,
|
||||
} from "lucide-react";
|
||||
|
||||
// Типы для данных заказов
|
||||
interface SupplyOrderItem {
|
||||
id: string;
|
||||
quantity: number;
|
||||
@ -36,7 +40,6 @@ interface SupplyOrderItem {
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
category?: {
|
||||
id: string;
|
||||
name: string;
|
||||
@ -46,20 +49,15 @@ interface SupplyOrderItem {
|
||||
|
||||
interface SupplyOrder {
|
||||
id: string;
|
||||
organizationId: string;
|
||||
deliveryDate: string;
|
||||
status: "PENDING" | "CONFIRMED" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";
|
||||
status: string;
|
||||
totalAmount: number;
|
||||
totalItems: number;
|
||||
fulfillmentCenterId?: string;
|
||||
deliveryDate: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
partner: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
inn: string;
|
||||
address?: string;
|
||||
phones?: string[];
|
||||
emails?: string[];
|
||||
};
|
||||
@ -78,8 +76,96 @@ interface SupplyOrder {
|
||||
items: SupplyOrderItem[];
|
||||
}
|
||||
|
||||
// Компонент для заголовка таблицы
|
||||
const TableHeader = ({
|
||||
children,
|
||||
field,
|
||||
sortable = false,
|
||||
sortField,
|
||||
sortOrder,
|
||||
onSort,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
field: string;
|
||||
sortable?: boolean;
|
||||
sortField?: string;
|
||||
sortOrder?: "asc" | "desc";
|
||||
onSort?: (field: string) => void;
|
||||
}) => (
|
||||
<div
|
||||
className={`px-3 py-2 text-xs font-bold text-white flex items-center justify-between ${
|
||||
sortable ? "cursor-pointer hover:bg-white/5" : ""
|
||||
}`}
|
||||
onClick={() => sortable && onSort && onSort(field)}
|
||||
>
|
||||
<span>{children}</span>
|
||||
{sortable && (
|
||||
<ArrowUpDown
|
||||
className={`h-3 w-3 ml-1 ${
|
||||
sortField === field ? "text-blue-400" : "text-white/40"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
// Компонент для статистических карточек
|
||||
const StatsCard = ({
|
||||
title,
|
||||
value,
|
||||
change = 0,
|
||||
icon: Icon,
|
||||
iconColor = "text-blue-400",
|
||||
iconBg = "bg-blue-500/20",
|
||||
subtitle,
|
||||
}: {
|
||||
title: string;
|
||||
value: string | number;
|
||||
change?: number;
|
||||
icon: React.ComponentType<any>;
|
||||
iconColor?: string;
|
||||
iconBg?: string;
|
||||
subtitle?: string;
|
||||
}) => (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className={`p-2 rounded-lg ${iconBg}`}>
|
||||
<Icon className={`h-5 w-5 ${iconColor}`} />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-white/60 text-sm">{title}</p>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-white text-xl font-bold">{value}</p>
|
||||
{change !== 0 && (
|
||||
<div className="flex items-center space-x-1">
|
||||
{change > 0 ? (
|
||||
<TrendingUp className="h-3 w-3 text-green-400" />
|
||||
) : (
|
||||
<TrendingDown className="h-3 w-3 text-red-400" />
|
||||
)}
|
||||
<span
|
||||
className={`text-xs font-medium ${
|
||||
change > 0 ? "text-green-400" : "text-red-400"
|
||||
}`}
|
||||
>
|
||||
{Math.abs(change)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{subtitle && <p className="text-white/40 text-xs mt-1">{subtitle}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
export function RealSupplyOrdersTab() {
|
||||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [sortField, setSortField] = useState<string>("createdAt");
|
||||
const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc");
|
||||
const { user } = useAuth();
|
||||
|
||||
const { data, loading, error, refetch } = useQuery(GET_SUPPLY_ORDERS, {
|
||||
@ -94,7 +180,7 @@ export function RealSupplyOrdersTab() {
|
||||
onCompleted: (data) => {
|
||||
if (data.updateSupplyOrderStatus.success) {
|
||||
toast.success(data.updateSupplyOrderStatus.message);
|
||||
refetch(); // Обновляем список заказов
|
||||
refetch();
|
||||
} else {
|
||||
toast.error(data.updateSupplyOrderStatus.message);
|
||||
}
|
||||
@ -109,14 +195,14 @@ export function RealSupplyOrdersTab() {
|
||||
// Получаем ID текущей организации (поставщика)
|
||||
const currentOrganizationId = user?.organization?.id;
|
||||
|
||||
// Фильтруем заказы где текущая организация является поставщиком (заказы К поставщику)
|
||||
// Фильтруем заказы где текущая организация является поставщиком
|
||||
const incomingSupplyOrders: SupplyOrder[] = (data?.supplyOrders || []).filter(
|
||||
(order: SupplyOrder) => {
|
||||
// Показываем заказы где текущий поставщик указан как поставщик (partnerId)
|
||||
return order.partner.id === currentOrganizationId;
|
||||
}
|
||||
);
|
||||
|
||||
// Функции для работы с таблицей
|
||||
const toggleOrderExpansion = (orderId: string) => {
|
||||
const newExpanded = new Set(expandedOrders);
|
||||
if (newExpanded.has(orderId)) {
|
||||
@ -127,66 +213,85 @@ export function RealSupplyOrdersTab() {
|
||||
setExpandedOrders(newExpanded);
|
||||
};
|
||||
|
||||
const handleStatusUpdate = async (
|
||||
orderId: string,
|
||||
newStatus: SupplyOrder["status"]
|
||||
) => {
|
||||
const handleSort = (field: string) => {
|
||||
if (sortField === field) {
|
||||
setSortOrder(sortOrder === "asc" ? "desc" : "asc");
|
||||
} else {
|
||||
setSortField(field);
|
||||
setSortOrder("asc");
|
||||
}
|
||||
};
|
||||
|
||||
const handleStatusUpdate = async (orderId: string, status: string) => {
|
||||
try {
|
||||
await updateSupplyOrderStatus({
|
||||
variables: {
|
||||
id: orderId,
|
||||
status: newStatus,
|
||||
status,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error updating status:", error);
|
||||
console.error("Error updating order status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: SupplyOrder["status"]) => {
|
||||
const statusMap = {
|
||||
PENDING: {
|
||||
label: "Ожидает одобрения",
|
||||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
icon: Clock,
|
||||
},
|
||||
CONFIRMED: {
|
||||
label: "Одобрена",
|
||||
color: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||
icon: CheckCircle,
|
||||
},
|
||||
IN_TRANSIT: {
|
||||
label: "В пути",
|
||||
color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||
icon: Truck,
|
||||
},
|
||||
DELIVERED: {
|
||||
label: "Доставлена",
|
||||
color: "bg-purple-500/20 text-purple-300 border-purple-500/30",
|
||||
icon: Package2,
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Отклонена",
|
||||
color: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||
icon: XCircle,
|
||||
},
|
||||
};
|
||||
|
||||
const { label, color, icon: Icon } = statusMap[status];
|
||||
|
||||
// Фильтрация и сортировка заказов
|
||||
const filteredAndSortedOrders = incomingSupplyOrders
|
||||
.filter((order) => {
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
return (
|
||||
<Badge className={`${color} border flex items-center gap-1`}>
|
||||
<Icon className="h-3 w-3" />
|
||||
{label}
|
||||
</Badge>
|
||||
order.id.toLowerCase().includes(searchLower) ||
|
||||
(order.organization.name || order.organization.fullName || "")
|
||||
.toLowerCase()
|
||||
.includes(searchLower) ||
|
||||
order.items.some(
|
||||
(item) =>
|
||||
item.product.name.toLowerCase().includes(searchLower) ||
|
||||
item.product.article.toLowerCase().includes(searchLower)
|
||||
)
|
||||
);
|
||||
};
|
||||
})
|
||||
.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
||||
switch (sortField) {
|
||||
case "organization":
|
||||
aValue = a.organization.name || a.organization.fullName || "";
|
||||
bValue = b.organization.name || b.organization.fullName || "";
|
||||
break;
|
||||
case "totalAmount":
|
||||
aValue = a.totalAmount;
|
||||
bValue = b.totalAmount;
|
||||
break;
|
||||
case "totalItems":
|
||||
aValue = a.totalItems;
|
||||
bValue = b.totalItems;
|
||||
break;
|
||||
case "deliveryDate":
|
||||
aValue = new Date(a.deliveryDate).getTime();
|
||||
bValue = new Date(b.deliveryDate).getTime();
|
||||
break;
|
||||
case "status":
|
||||
aValue = a.status;
|
||||
bValue = b.status;
|
||||
break;
|
||||
default:
|
||||
aValue = new Date(a.createdAt).getTime();
|
||||
bValue = new Date(b.createdAt).getTime();
|
||||
}
|
||||
|
||||
if (aValue < bValue) return sortOrder === "asc" ? -1 : 1;
|
||||
if (aValue > bValue) return sortOrder === "asc" ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
// Функции форматирования
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
@ -208,7 +313,120 @@ export function RealSupplyOrdersTab() {
|
||||
});
|
||||
};
|
||||
|
||||
// Статистика для поставщика
|
||||
const formatNumber = (num: number) => {
|
||||
return new Intl.NumberFormat("ru-RU").format(num);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
PENDING: {
|
||||
label: "Ожидает",
|
||||
className: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||||
},
|
||||
CONFIRMED: {
|
||||
label: "Одобрена",
|
||||
className: "bg-green-500/20 text-green-300 border-green-500/30",
|
||||
},
|
||||
IN_TRANSIT: {
|
||||
label: "В пути",
|
||||
className: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||||
},
|
||||
DELIVERED: {
|
||||
label: "Доставлена",
|
||||
className: "bg-emerald-500/20 text-emerald-300 border-emerald-500/30",
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Отменена",
|
||||
className: "bg-red-500/20 text-red-300 border-red-500/30",
|
||||
},
|
||||
};
|
||||
|
||||
const config = statusConfig[status as keyof typeof statusConfig] || {
|
||||
label: status,
|
||||
className: "bg-gray-500/20 text-gray-300 border-gray-500/30",
|
||||
};
|
||||
|
||||
return (
|
||||
<Badge className={`${config.className} border text-xs`}>
|
||||
{config.label}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const getInitials = (name: string): string => {
|
||||
return name
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0))
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2);
|
||||
};
|
||||
|
||||
const getColorForOrder = (orderId: string): string => {
|
||||
const colors = [
|
||||
"bg-blue-500",
|
||||
"bg-green-500",
|
||||
"bg-purple-500",
|
||||
"bg-orange-500",
|
||||
"bg-pink-500",
|
||||
"bg-indigo-500",
|
||||
"bg-teal-500",
|
||||
"bg-red-500",
|
||||
];
|
||||
const hash = orderId
|
||||
.split("")
|
||||
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||
return colors[hash % colors.length];
|
||||
};
|
||||
|
||||
// Цветовые схемы для заказов
|
||||
const getColorScheme = (orderId: string) => {
|
||||
const colorSchemes = [
|
||||
{
|
||||
bg: "bg-blue-500/5",
|
||||
border: "border-blue-500/30",
|
||||
borderLeft: "border-l-blue-400",
|
||||
text: "text-blue-100",
|
||||
indicator: "bg-blue-400 border-blue-300",
|
||||
hover: "hover:bg-blue-500/10",
|
||||
header: "bg-blue-500/20 border-blue-500/40",
|
||||
},
|
||||
{
|
||||
bg: "bg-pink-500/5",
|
||||
border: "border-pink-500/30",
|
||||
borderLeft: "border-l-pink-400",
|
||||
text: "text-pink-100",
|
||||
indicator: "bg-pink-400 border-pink-300",
|
||||
hover: "hover:bg-pink-500/10",
|
||||
header: "bg-pink-500/20 border-pink-500/40",
|
||||
},
|
||||
{
|
||||
bg: "bg-emerald-500/5",
|
||||
border: "border-emerald-500/30",
|
||||
borderLeft: "border-l-emerald-400",
|
||||
text: "text-emerald-100",
|
||||
indicator: "bg-emerald-400 border-emerald-300",
|
||||
hover: "hover:bg-emerald-500/10",
|
||||
header: "bg-emerald-500/20 border-emerald-500/40",
|
||||
},
|
||||
{
|
||||
bg: "bg-orange-500/5",
|
||||
border: "border-orange-500/30",
|
||||
borderLeft: "border-l-orange-400",
|
||||
text: "text-orange-100",
|
||||
indicator: "bg-orange-400 border-orange-300",
|
||||
hover: "hover:bg-orange-500/10",
|
||||
header: "bg-orange-500/20 border-orange-500/40",
|
||||
},
|
||||
];
|
||||
|
||||
const hash = orderId
|
||||
.split("")
|
||||
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||
return colorSchemes[hash % colorSchemes.length];
|
||||
};
|
||||
|
||||
// Подсчет статистики
|
||||
const totalOrders = incomingSupplyOrders.length;
|
||||
const totalAmount = incomingSupplyOrders.reduce(
|
||||
(sum, order) => sum + order.totalAmount,
|
||||
@ -228,6 +446,22 @@ export function RealSupplyOrdersTab() {
|
||||
(order) => order.status === "IN_TRANSIT"
|
||||
).length;
|
||||
|
||||
// Подсчет общих итогов для отображения в строке итогов
|
||||
const totals = {
|
||||
orders: filteredAndSortedOrders.length,
|
||||
amount: filteredAndSortedOrders.reduce(
|
||||
(sum, order) => sum + order.totalAmount,
|
||||
0
|
||||
),
|
||||
items: filteredAndSortedOrders.reduce(
|
||||
(sum, order) => sum + order.totalItems,
|
||||
0
|
||||
),
|
||||
pending: filteredAndSortedOrders.filter(
|
||||
(order) => order.status === "PENDING"
|
||||
).length,
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
@ -250,9 +484,10 @@ export function RealSupplyOrdersTab() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Статистика входящих заявок */}
|
||||
<StatsGrid>
|
||||
<div className="h-full flex flex-col overflow-hidden">
|
||||
{/* Статистические карточки - 30% экрана */}
|
||||
<div className="flex-shrink-0 mb-4" style={{ maxHeight: "30vh" }}>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<StatsCard
|
||||
title="Всего заявок"
|
||||
value={totalOrders}
|
||||
@ -261,7 +496,6 @@ export function RealSupplyOrdersTab() {
|
||||
iconBg="bg-orange-500/20"
|
||||
subtitle="Заявки от селлеров"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Ожидают одобрения"
|
||||
value={pendingOrders}
|
||||
@ -270,7 +504,6 @@ export function RealSupplyOrdersTab() {
|
||||
iconBg="bg-blue-500/20"
|
||||
subtitle="Требуют решения"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Одобрено"
|
||||
value={approvedOrders}
|
||||
@ -279,7 +512,6 @@ export function RealSupplyOrdersTab() {
|
||||
iconBg="bg-green-500/20"
|
||||
subtitle="Готовы к отправке"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="В пути"
|
||||
value={inTransitOrders}
|
||||
@ -288,7 +520,6 @@ export function RealSupplyOrdersTab() {
|
||||
iconBg="bg-yellow-500/20"
|
||||
subtitle="Доставляются"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Общая сумма"
|
||||
value={formatCurrency(totalAmount)}
|
||||
@ -297,7 +528,6 @@ export function RealSupplyOrdersTab() {
|
||||
iconBg="bg-green-500/20"
|
||||
subtitle="Стоимость заявок"
|
||||
/>
|
||||
|
||||
<StatsCard
|
||||
title="Всего единиц"
|
||||
value={totalItems}
|
||||
@ -306,61 +536,164 @@ export function RealSupplyOrdersTab() {
|
||||
iconBg="bg-purple-500/20"
|
||||
subtitle="Количество товаров"
|
||||
/>
|
||||
</StatsGrid>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Список входящих заявок */}
|
||||
{incomingSupplyOrders.length === 0 ? (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-8">
|
||||
{/* Основная таблица - 70% экрана */}
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 flex-1 flex flex-col overflow-hidden">
|
||||
{/* Шапка таблицы с поиском */}
|
||||
<div className="p-4 border-b border-white/10 flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-base font-semibold text-white flex items-center space-x-2">
|
||||
<Store className="h-4 w-4 text-blue-400" />
|
||||
<span>Заявки на расходники</span>
|
||||
</h2>
|
||||
|
||||
{/* Поиск */}
|
||||
<div className="relative mx-2.5 flex-1 max-w-xs">
|
||||
<Search className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-3.5 w-3.5 text-white/40" />
|
||||
<Input
|
||||
placeholder="Поиск по заявкам..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="pl-8 h-8 text-sm glass-input text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-blue-500/20 text-blue-300 text-xs"
|
||||
>
|
||||
{filteredAndSortedOrders.length} заявок
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Заголовки таблицы */}
|
||||
<div className="flex-shrink-0 bg-blue-500/20 border-b border-blue-500/40">
|
||||
<div className="grid grid-cols-7 gap-0">
|
||||
<TableHeader
|
||||
field="id"
|
||||
sortable
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
>
|
||||
№ / ID
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
field="organization"
|
||||
sortable
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
>
|
||||
Заказчик
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
field="deliveryDate"
|
||||
sortable
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
>
|
||||
Дата поставки
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
field="totalItems"
|
||||
sortable
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
>
|
||||
Количество
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
field="totalAmount"
|
||||
sortable
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
>
|
||||
Сумма
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
field="status"
|
||||
sortable
|
||||
sortField={sortField}
|
||||
sortOrder={sortOrder}
|
||||
onSort={handleSort}
|
||||
>
|
||||
Статус
|
||||
</TableHeader>
|
||||
<TableHeader field="actions">Действия</TableHeader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Строка с итогами */}
|
||||
<div className="flex-shrink-0 bg-blue-500/25 border-b border-blue-500/50">
|
||||
<div className="grid grid-cols-7 gap-0">
|
||||
<div className="px-3 py-2 text-xs font-bold text-blue-300">
|
||||
ИТОГО ({totals.orders})
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs font-bold text-white">
|
||||
{totals.orders} заказчиков
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs font-bold text-white">-</div>
|
||||
<div className="px-3 py-2 text-xs font-bold text-white">
|
||||
{formatNumber(totals.items)} шт
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs font-bold text-white">
|
||||
{formatCurrency(totals.amount)}
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs font-bold text-white">
|
||||
{totals.pending} ожидают
|
||||
</div>
|
||||
<div className="px-3 py-2 text-xs font-bold text-white">-</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Скроллируемый контент таблицы */}
|
||||
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-thumb-white/20 scrollbar-track-transparent">
|
||||
{filteredAndSortedOrders.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
<div className="text-center">
|
||||
<Wrench className="h-16 w-16 text-white/20 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-semibold text-white mb-2">
|
||||
Пока нет заявок
|
||||
</h3>
|
||||
<p className="text-white/60">
|
||||
Здесь будут отображаться заявки от селлеров на поставку товаров
|
||||
<Wrench className="h-12 w-12 text-white/40 mx-auto mb-4" />
|
||||
<p className="text-white/60 font-medium">
|
||||
{incomingSupplyOrders.length === 0
|
||||
? "Нет заявок на расходники"
|
||||
: "Заявки не найдены"}
|
||||
</p>
|
||||
<p className="text-white/40 text-sm mt-2">
|
||||
{incomingSupplyOrders.length === 0
|
||||
? "Здесь будут отображаться заявки от селлеров"
|
||||
: searchTerm
|
||||
? "Попробуйте изменить поисковый запрос"
|
||||
: "Данные о заявках будут отображены здесь"}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
) : (
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 overflow-hidden">
|
||||
<div className="overflow-x-auto">
|
||||
<table className="w-full">
|
||||
<thead>
|
||||
<tr className="border-b border-white/20">
|
||||
<th className="text-left p-4 text-white font-semibold">ID</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Заказчик
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Дата поставки
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Дата заявки
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Количество
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Сумма
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Статус
|
||||
</th>
|
||||
<th className="text-left p-4 text-white font-semibold">
|
||||
Действия
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{incomingSupplyOrders.map((order) => {
|
||||
filteredAndSortedOrders.map((order, index) => {
|
||||
const colorScheme = getColorScheme(order.id);
|
||||
const isOrderExpanded = expandedOrders.has(order.id);
|
||||
const organizationName =
|
||||
order.organization.name ||
|
||||
order.organization.fullName ||
|
||||
"Заказчик";
|
||||
|
||||
return (
|
||||
<React.Fragment key={order.id}>
|
||||
<div
|
||||
key={order.id}
|
||||
className={`border-b ${colorScheme.border} ${colorScheme.hover} transition-colors border-l-8 ${colorScheme.borderLeft} ${colorScheme.bg} shadow-sm hover:shadow-md`}
|
||||
>
|
||||
{/* Основная строка заказа */}
|
||||
<tr className="border-b border-white/10 hover:bg-white/5 transition-colors">
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="grid grid-cols-7 gap-0">
|
||||
<div className="px-3 py-2.5 flex items-center space-x-2">
|
||||
<span className="text-white/60 text-xs">
|
||||
{filteredAndSortedOrders.length - index}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => toggleOrderExpansion(order.id)}
|
||||
className="text-white/60 hover:text-white"
|
||||
@ -371,55 +704,57 @@ export function RealSupplyOrdersTab() {
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
)}
|
||||
</button>
|
||||
<span className="text-white font-medium">
|
||||
<span className="text-white font-medium text-xs">
|
||||
{order.id.slice(-8)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center space-x-2">
|
||||
<User className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white font-medium">
|
||||
{order.organization.name ||
|
||||
order.organization.fullName ||
|
||||
"Заказчик"}
|
||||
|
||||
<div className="px-3 py-2.5 flex items-center space-x-2">
|
||||
<Avatar className="w-6 h-6">
|
||||
<AvatarFallback
|
||||
className={`${getColorForOrder(
|
||||
order.id
|
||||
)} text-white text-xs`}
|
||||
>
|
||||
{getInitials(organizationName)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div>
|
||||
<span className="text-white font-medium text-sm">
|
||||
{organizationName}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-white/60 text-sm">
|
||||
Тип: {order.organization.type}
|
||||
<p className="text-white/60 text-xs">
|
||||
{order.organization.type}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
</div>
|
||||
|
||||
<div className="px-3 py-2.5 flex items-center space-x-2">
|
||||
<Calendar className="h-4 w-4 text-white/40" />
|
||||
<span className="text-white font-semibold">
|
||||
<span className="text-white font-semibold text-sm">
|
||||
{formatDate(order.deliveryDate)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<span className="text-white/80">
|
||||
{formatDateTime(order.createdAt)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<span className="text-white font-semibold">
|
||||
|
||||
<div className="px-3 py-2.5">
|
||||
<span className="text-white font-semibold text-sm">
|
||||
{order.totalItems} шт
|
||||
</span>
|
||||
</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
</div>
|
||||
|
||||
<div className="px-3 py-2.5 flex items-center space-x-2">
|
||||
<DollarSign className="h-4 w-4 text-white/40" />
|
||||
<span className="text-green-400 font-bold">
|
||||
<span className="text-green-400 font-bold text-sm">
|
||||
{formatCurrency(order.totalAmount)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="p-4">{getStatusBadge(order.status)}</td>
|
||||
<td className="p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
|
||||
<div className="px-3 py-2.5">
|
||||
{getStatusBadge(order.status)}
|
||||
</div>
|
||||
|
||||
<div className="px-3 py-2.5">
|
||||
<div className="flex items-center space-x-1">
|
||||
{order.status === "PENDING" && (
|
||||
<>
|
||||
<Button
|
||||
@ -428,9 +763,9 @@ export function RealSupplyOrdersTab() {
|
||||
handleStatusUpdate(order.id, "CONFIRMED")
|
||||
}
|
||||
disabled={updating}
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30"
|
||||
className="bg-green-500/20 hover:bg-green-500/30 text-green-300 border border-green-500/30 text-xs px-2 py-1 h-6"
|
||||
>
|
||||
<CheckCircle className="h-4 w-4 mr-1" />
|
||||
<CheckCircle className="h-3 w-3 mr-1" />
|
||||
Одобрить
|
||||
</Button>
|
||||
<Button
|
||||
@ -439,10 +774,10 @@ export function RealSupplyOrdersTab() {
|
||||
handleStatusUpdate(order.id, "CANCELLED")
|
||||
}
|
||||
disabled={updating}
|
||||
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border border-red-500/30"
|
||||
className="bg-red-500/20 hover:bg-red-500/30 text-red-300 border border-red-500/30 text-xs px-2 py-1 h-6"
|
||||
>
|
||||
<XCircle className="h-4 w-4 mr-1" />
|
||||
Отказать
|
||||
<XCircle className="h-3 w-3 mr-1" />
|
||||
Отклонить
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
@ -453,40 +788,48 @@ export function RealSupplyOrdersTab() {
|
||||
handleStatusUpdate(order.id, "IN_TRANSIT")
|
||||
}
|
||||
disabled={updating}
|
||||
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-300 border border-yellow-500/30"
|
||||
className="bg-yellow-500/20 hover:bg-yellow-500/30 text-yellow-300 border border-yellow-500/30 text-xs px-2 py-1 h-6"
|
||||
>
|
||||
<Truck className="h-4 w-4 mr-1" />
|
||||
<Truck className="h-3 w-3 mr-1" />
|
||||
Отправить
|
||||
</Button>
|
||||
)}
|
||||
{order.status === "CANCELLED" && (
|
||||
<span className="text-red-400 text-sm">
|
||||
<span className="text-red-400 text-xs">
|
||||
Отклонена
|
||||
</span>
|
||||
)}
|
||||
{order.status === "IN_TRANSIT" && (
|
||||
<span className="text-yellow-400 text-sm">
|
||||
<span className="text-yellow-400 text-xs">
|
||||
В пути
|
||||
</span>
|
||||
)}
|
||||
{order.status === "DELIVERED" && (
|
||||
<span className="text-green-400 text-sm">
|
||||
<span className="text-green-400 text-xs">
|
||||
Доставлена
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Развернутая информация о заказе */}
|
||||
{isOrderExpanded && (
|
||||
<tr>
|
||||
<td colSpan={8} className="p-0">
|
||||
<div className="bg-white/5 border-t border-white/10">
|
||||
<div
|
||||
className={`${colorScheme.bg} border-t ${colorScheme.border}`}
|
||||
>
|
||||
<div className="p-6">
|
||||
<h4 className="text-white font-semibold mb-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h4 className="text-white font-semibold">
|
||||
Состав заявки:
|
||||
</h4>
|
||||
<div className="flex items-center space-x-2 text-white/60 text-sm">
|
||||
<Calendar className="h-4 w-4" />
|
||||
<span>
|
||||
Дата создания: {formatDateTime(order.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{order.items.map((item) => (
|
||||
<Card
|
||||
@ -528,17 +871,14 @@ export function RealSupplyOrdersTab() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
})
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -8,10 +8,19 @@ import { Sidebar } from "@/components/dashboard/sidebar";
|
||||
import { useSidebar } from "@/hooks/useSidebar";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Plus, Package, Wrench, ChevronDown, AlertTriangle } from "lucide-react";
|
||||
import {
|
||||
Plus,
|
||||
Package,
|
||||
Wrench,
|
||||
ChevronDown,
|
||||
AlertTriangle,
|
||||
} from "lucide-react";
|
||||
import { GET_PENDING_SUPPLIES_COUNT } from "@/graphql/queries";
|
||||
import { FulfillmentSuppliesTab } from "./fulfillment-supplies/fulfillment-supplies-tab";
|
||||
import { MarketplaceSuppliesTab } from "./marketplace-supplies/marketplace-supplies-tab";
|
||||
import { FulfillmentGoodsTab } from "./fulfillment-supplies/fulfillment-goods-tab";
|
||||
import { RealSupplyOrdersTab } from "./fulfillment-supplies/real-supply-orders-tab";
|
||||
import { SellerSupplyOrdersTab } from "./fulfillment-supplies/seller-supply-orders-tab";
|
||||
import { AllSuppliesTab } from "./fulfillment-supplies/all-supplies-tab";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@ -22,13 +31,14 @@ import {
|
||||
export function SuppliesDashboard() {
|
||||
const { getSidebarMargin } = useSidebar();
|
||||
const searchParams = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState("fulfillment");
|
||||
const [activeTab, setActiveTab] = useState("all");
|
||||
const { user } = useAuth();
|
||||
|
||||
// Загружаем счетчик поставок, требующих одобрения
|
||||
const { data: pendingData } = useQuery(GET_PENDING_SUPPLIES_COUNT, {
|
||||
pollInterval: 30000, // Обновляем каждые 30 секунд
|
||||
fetchPolicy: 'cache-first',
|
||||
errorPolicy: 'ignore',
|
||||
fetchPolicy: "cache-first",
|
||||
errorPolicy: "ignore",
|
||||
});
|
||||
|
||||
const pendingCount = pendingData?.pendingSuppliesCount;
|
||||
@ -38,10 +48,15 @@ export function SuppliesDashboard() {
|
||||
useEffect(() => {
|
||||
const tab = searchParams.get("tab");
|
||||
if (tab === "consumables") {
|
||||
setActiveTab("fulfillment"); // Устанавливаем основную вкладку "Поставки на ФФ"
|
||||
setActiveTab("supplies");
|
||||
} else if (tab === "goods") {
|
||||
setActiveTab("goods");
|
||||
}
|
||||
}, [searchParams]);
|
||||
|
||||
// Определяем тип организации для выбора правильного компонента
|
||||
const isWholesale = user?.organization?.type === "WHOLESALE";
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
@ -54,41 +69,73 @@ export function SuppliesDashboard() {
|
||||
<Alert className="mb-4 bg-blue-500/20 border-blue-400/30 text-blue-300 animate-pulse">
|
||||
<AlertTriangle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
У вас есть {pendingCount.total} элемент{pendingCount.total > 1 ? (pendingCount.total < 5 ? 'а' : 'ов') : ''}, требующ{pendingCount.total > 1 ? 'их' : 'ий'} одобрения:
|
||||
{pendingCount.supplyOrders > 0 && ` ${pendingCount.supplyOrders} заказ${pendingCount.supplyOrders > 1 ? (pendingCount.supplyOrders < 5 ? 'а' : 'ов') : ''} поставок`}
|
||||
{pendingCount.incomingRequests > 0 && pendingCount.supplyOrders > 0 && ', '}
|
||||
{pendingCount.incomingRequests > 0 && ` ${pendingCount.incomingRequests} заявк${pendingCount.incomingRequests > 1 ? (pendingCount.incomingRequests < 5 ? 'и' : '') : 'а'} на партнерство`}
|
||||
У вас есть {pendingCount.total} элемент
|
||||
{pendingCount.total > 1
|
||||
? pendingCount.total < 5
|
||||
? "а"
|
||||
: "ов"
|
||||
: ""}
|
||||
, требующ{pendingCount.total > 1 ? "их" : "ий"} одобрения:
|
||||
{pendingCount.supplyOrders > 0 &&
|
||||
` ${pendingCount.supplyOrders} заказ${
|
||||
pendingCount.supplyOrders > 1
|
||||
? pendingCount.supplyOrders < 5
|
||||
? "а"
|
||||
: "ов"
|
||||
: ""
|
||||
} поставок`}
|
||||
{pendingCount.incomingRequests > 0 &&
|
||||
pendingCount.supplyOrders > 0 &&
|
||||
", "}
|
||||
{pendingCount.incomingRequests > 0 &&
|
||||
` ${pendingCount.incomingRequests} заявк${
|
||||
pendingCount.incomingRequests > 1
|
||||
? pendingCount.incomingRequests < 5
|
||||
? "и"
|
||||
: ""
|
||||
: "а"
|
||||
} на партнерство`}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Главные вкладки с кнопкой создания */}
|
||||
{/* Основные вкладки с кнопкой создания */}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
className="w-full h-full flex flex-col"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-1 flex-wrap gap-2">
|
||||
<TabsList className={`grid grid-cols-2 bg-white/10 backdrop-blur border-white/20 w-fit text-sm ${hasPendingItems ? 'ring-2 ring-blue-400/50' : ''}`}>
|
||||
<TabsTrigger
|
||||
value="fulfillment"
|
||||
className={`data-[state=active]:bg-gradient-to-r data-[state=active]:from-purple-500 data-[state=active]:to-pink-500 data-[state=active]:text-white text-white/60 px-3 sm:px-6 relative ${pendingCount?.supplyOrders > 0 ? 'animate-pulse' : ''}`}
|
||||
<TabsList
|
||||
className={`grid grid-cols-3 bg-white/10 backdrop-blur border-white/20 w-fit text-sm ${
|
||||
hasPendingItems ? "ring-2 ring-blue-400/50" : ""
|
||||
}`}
|
||||
>
|
||||
<span className="hidden sm:inline">Поставки на ФФ</span>
|
||||
<span className="sm:hidden">ФФ</span>
|
||||
<TabsTrigger
|
||||
value="all"
|
||||
className="data-[state=active]:bg-gradient-to-r data-[state=active]:from-purple-500 data-[state=active]:to-pink-500 data-[state=active]:text-white text-white/60 px-3 sm:px-6"
|
||||
>
|
||||
Все
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="goods"
|
||||
className="data-[state=active]:bg-gradient-to-r data-[state=active]:from-purple-500 data-[state=active]:to-pink-500 data-[state=active]:text-white text-white/60 px-3 sm:px-6"
|
||||
>
|
||||
Товар
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="supplies"
|
||||
className={`data-[state=active]:bg-gradient-to-r data-[state=active]:from-purple-500 data-[state=active]:to-pink-500 data-[state=active]:text-white text-white/60 px-3 sm:px-6 relative ${
|
||||
pendingCount?.supplyOrders > 0 ? "animate-pulse" : ""
|
||||
}`}
|
||||
>
|
||||
Расходники
|
||||
{pendingCount?.supplyOrders > 0 && (
|
||||
<div className="absolute -top-1 -right-1 w-5 h-5 bg-blue-500 rounded-full flex items-center justify-center text-xs font-bold text-white">
|
||||
{pendingCount.supplyOrders}
|
||||
</div>
|
||||
)}
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="marketplace"
|
||||
className="data-[state=active]:bg-gradient-to-r data-[state=active]:from-purple-500 data-[state=active]:to-pink-500 data-[state=active]:text-white text-white/60 px-3 sm:px-6"
|
||||
>
|
||||
<span className="hidden sm:inline">Поставки на Маркетплейсы</span>
|
||||
<span className="sm:hidden">МП</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<DropdownMenu>
|
||||
@ -129,25 +176,25 @@ export function SuppliesDashboard() {
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
|
||||
<TabsContent
|
||||
value="fulfillment"
|
||||
className="mt-0 flex-1 overflow-hidden"
|
||||
>
|
||||
<FulfillmentSuppliesTab
|
||||
defaultSubTab={
|
||||
searchParams.get("tab") === "consumables"
|
||||
? "supplies"
|
||||
: undefined
|
||||
}
|
||||
<TabsContent value="all" className="mt-0 flex-1 overflow-hidden">
|
||||
<AllSuppliesTab
|
||||
pendingSupplyOrders={pendingCount?.supplyOrders || 0}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="goods" className="mt-0 flex-1 overflow-hidden">
|
||||
<FulfillmentGoodsTab />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="marketplace"
|
||||
value="supplies"
|
||||
className="mt-0 flex-1 overflow-hidden"
|
||||
>
|
||||
<MarketplaceSuppliesTab />
|
||||
{isWholesale ? (
|
||||
<RealSupplyOrdersTab />
|
||||
) : (
|
||||
<SellerSupplyOrdersTab />
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user