Обновлен компонент CreateSupplyPage: улучшена структура кода и стили, добавлены новые функции для обработки товаров и поставок. Оптимизирован интерфейс с использованием новых компонентов и улучшена логика отображения данных. Исправлены ошибки и улучшена читаемость кода.

This commit is contained in:
Veronika Smirnova
2025-07-25 15:23:11 +03:00
parent 072d330202
commit 7d9b76a792
2 changed files with 438 additions and 344 deletions

View File

@ -1,157 +1,184 @@
"use client" "use client";
import React, { useState } from 'react' import React, { useState } from "react";
import { Sidebar } from '@/components/dashboard/sidebar' import { Sidebar } from "@/components/dashboard/sidebar";
import { useSidebar } from '@/hooks/useSidebar' import { useSidebar } from "@/hooks/useSidebar";
import { useRouter } from 'next/navigation' import { useRouter } from "next/navigation";
import { DirectSupplyCreation } from './direct-supply-creation' import { DirectSupplyCreation } from "./direct-supply-creation";
import { WholesalerProductsPage } from './wholesaler-products-page' import { WholesalerProductsPage } from "./wholesaler-products-page";
import { TabsHeader } from './tabs-header' import { TabsHeader } from "./tabs-header";
import { WholesalerGrid } from './wholesaler-grid' import { WholesalerGrid } from "./wholesaler-grid";
import { CartSummary } from './cart-summary' import { CartSummary } from "./cart-summary";
import { FloatingCart } from './floating-cart' import { FloatingCart } from "./floating-cart";
import { import {
WholesalerForCreation, WholesalerForCreation,
WholesalerProduct, WholesalerProduct,
SelectedProduct, SelectedProduct,
CounterpartyWholesaler CounterpartyWholesaler,
} from './types' } from "./types";
import { useQuery } from '@apollo/client' import { useQuery } from "@apollo/client";
import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries' import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from "@/graphql/queries";
export function CreateSupplyPage() { export function CreateSupplyPage() {
const router = useRouter() const router = useRouter();
const { getSidebarMargin } = useSidebar() const { getSidebarMargin } = useSidebar();
const [activeTab, setActiveTab] = useState<'cards' | 'wholesaler'>('cards') const [activeTab, setActiveTab] = useState<"cards" | "wholesaler">("cards");
const [selectedWholesaler, setSelectedWholesaler] = useState<WholesalerForCreation | null>(null) const [selectedWholesaler, setSelectedWholesaler] =
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>([]) useState<WholesalerForCreation | null>(null);
const [showSummary, setShowSummary] = useState(false) const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>(
const [searchQuery, setSearchQuery] = useState('') []
const [canCreateSupply, setCanCreateSupply] = useState(false) );
const [isCreatingSupply, setIsCreatingSupply] = useState(false) const [showSummary, setShowSummary] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const [canCreateSupply, setCanCreateSupply] = useState(false);
const [isCreatingSupply, setIsCreatingSupply] = useState(false);
// Загружаем контрагентов-оптовиков // Загружаем контрагентов-оптовиков
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES) const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(
GET_MY_COUNTERPARTIES
);
// Загружаем товары для выбранного оптовика // Загружаем товары для выбранного оптовика
const { data: productsData, loading: productsLoading } = useQuery(GET_ALL_PRODUCTS, { const { data: productsData, loading: productsLoading } = useQuery(
GET_ALL_PRODUCTS,
{
skip: !selectedWholesaler, skip: !selectedWholesaler,
variables: { search: null, category: null } variables: { search: null, category: null },
}) }
);
// Фильтруем только оптовиков // Фильтруем только оптовиков
const wholesalers: CounterpartyWholesaler[] = (counterpartiesData?.myCounterparties || []) const wholesalers: CounterpartyWholesaler[] = (
.filter((org: { type: string }) => org.type === 'WHOLESALE') counterpartiesData?.myCounterparties || []
).filter((org: { type: string }) => org.type === "WHOLESALE");
// Фильтруем товары по выбранному оптовику // Фильтруем товары по выбранному оптовику
const wholesalerProducts: WholesalerProduct[] = selectedWholesaler const wholesalerProducts: WholesalerProduct[] = selectedWholesaler
? (productsData?.allProducts || []).filter((product: { organization: { id: string } }) => ? (productsData?.allProducts || []).filter(
(product: { organization: { id: string } }) =>
product.organization.id === selectedWholesaler.id product.organization.id === selectedWholesaler.id
) )
: [] : [];
const formatCurrency = (amount: number) => { const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('ru-RU', { return new Intl.NumberFormat("ru-RU", {
style: 'currency', style: "currency",
currency: 'RUB', currency: "RUB",
minimumFractionDigits: 0 minimumFractionDigits: 0,
}).format(amount) }).format(amount);
} };
const updateProductQuantity = (productId: string, quantity: number) => { const updateProductQuantity = (productId: string, quantity: number) => {
const product = wholesalerProducts.find((p) => p.id === productId) const product = wholesalerProducts.find((p) => p.id === productId);
if (!product || !selectedWholesaler) return if (!product || !selectedWholesaler) return;
setSelectedProducts(prev => { setSelectedProducts((prev) => {
const existing = prev.find(p => p.id === productId && p.wholesalerId === selectedWholesaler.id) const existing = prev.find(
(p) => p.id === productId && p.wholesalerId === selectedWholesaler.id
);
if (quantity === 0) { if (quantity === 0) {
return prev.filter(p => !(p.id === productId && p.wholesalerId === selectedWholesaler.id)) return prev.filter(
(p) =>
!(p.id === productId && p.wholesalerId === selectedWholesaler.id)
);
} }
if (existing) { if (existing) {
return prev.map(p => return prev.map((p) =>
p.id === productId && p.wholesalerId === selectedWholesaler.id p.id === productId && p.wholesalerId === selectedWholesaler.id
? { ...p, selectedQuantity: quantity } ? { ...p, selectedQuantity: quantity }
: p : p
) );
} else { } else {
return [...prev, { return [
...prev,
{
...product, ...product,
selectedQuantity: quantity, selectedQuantity: quantity,
wholesalerId: selectedWholesaler.id, wholesalerId: selectedWholesaler.id,
wholesalerName: selectedWholesaler.name wholesalerName: selectedWholesaler.name,
}] },
} ];
})
} }
});
};
const getTotalAmount = () => { const getTotalAmount = () => {
return selectedProducts.reduce((sum, product) => { return selectedProducts.reduce((sum, product) => {
const discountedPrice = product.discount const discountedPrice = product.discount
? product.price * (1 - product.discount / 100) ? product.price * (1 - product.discount / 100)
: product.price : product.price;
return sum + (discountedPrice * product.selectedQuantity) return sum + discountedPrice * product.selectedQuantity;
}, 0) }, 0);
} };
const getTotalItems = () => { const getTotalItems = () => {
return selectedProducts.reduce((sum, product) => sum + product.selectedQuantity, 0) return selectedProducts.reduce(
} (sum, product) => sum + product.selectedQuantity,
0
);
};
const handleCreateSupply = () => { const handleCreateSupply = () => {
if (activeTab === 'cards') { if (activeTab === "cards") {
console.log('Создание поставки с карточками Wildberries') console.log("Создание поставки с карточками Wildberries");
} else { } else {
console.log('Создание поставки с товарами:', selectedProducts) console.log("Создание поставки с товарами:", selectedProducts);
}
router.push('/supplies')
} }
router.push("/supplies");
};
const handleGoBack = () => { const handleGoBack = () => {
if (selectedWholesaler) { if (selectedWholesaler) {
setSelectedWholesaler(null) setSelectedWholesaler(null);
setShowSummary(false) setShowSummary(false);
} else { } else {
router.push('/supplies') router.push("/supplies");
}
} }
};
const handleRemoveProduct = (productId: string, wholesalerId: string) => { const handleRemoveProduct = (productId: string, wholesalerId: string) => {
setSelectedProducts(prev => setSelectedProducts((prev) =>
prev.filter(p => !(p.id === productId && p.wholesalerId === wholesalerId)) prev.filter(
(p) => !(p.id === productId && p.wholesalerId === wholesalerId)
) )
} );
};
const handleCartQuantityChange = (productId: string, wholesalerId: string, quantity: number) => { const handleCartQuantityChange = (
setSelectedProducts(prev => productId: string,
prev.map(p => wholesalerId: string,
quantity: number
) => {
setSelectedProducts((prev) =>
prev.map((p) =>
p.id === productId && p.wholesalerId === wholesalerId p.id === productId && p.wholesalerId === wholesalerId
? { ...p, selectedQuantity: quantity } ? { ...p, selectedQuantity: quantity }
: p : p
) )
) );
} };
const handleSupplyComplete = () => { const handleSupplyComplete = () => {
router.push('/supplies') router.push("/supplies");
} };
const handleCreateSupplyClick = () => { const handleCreateSupplyClick = () => {
setIsCreatingSupply(true) setIsCreatingSupply(true);
} };
const handleCanCreateSupplyChange = (canCreate: boolean) => { const handleCanCreateSupplyChange = (canCreate: boolean) => {
setCanCreateSupply(canCreate) setCanCreateSupply(canCreate);
} };
const handleSupplyCompleted = () => { const handleSupplyCompleted = () => {
setIsCreatingSupply(false) setIsCreatingSupply(false);
handleSupplyComplete() handleSupplyComplete();
} };
// Рендер страницы товаров оптовика // Рендер страницы товаров оптовика
if (selectedWholesaler && activeTab === 'wholesaler') { if (selectedWholesaler && activeTab === "wholesaler") {
return ( return (
<WholesalerProductsPage <WholesalerProductsPage
selectedWholesaler={selectedWholesaler} selectedWholesaler={selectedWholesaler}
@ -165,25 +192,28 @@ export function CreateSupplyPage() {
setShowSummary={setShowSummary} setShowSummary={setShowSummary}
loading={productsLoading} loading={productsLoading}
/> />
) );
} }
// Главная страница с табами // Главная страница с табами
return ( return (
<div className="h-screen flex overflow-hidden"> <div className="h-screen flex overflow-hidden">
<Sidebar /> <Sidebar />
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-y-auto transition-all duration-300`}> <main
<div className="p-4 min-h-full"> className={`flex-1 ${getSidebarMargin()} overflow-hidden transition-all duration-300`}
style={{ padding: '1rem' }}
>
<div className="flex flex-col" style={{ height: 'calc(100vh - 2rem)' }}>
<TabsHeader <TabsHeader
activeTab={activeTab} activeTab={activeTab}
onTabChange={setActiveTab} onTabChange={setActiveTab}
onBack={() => router.push('/supplies')} onBack={() => router.push("/supplies")}
cartInfo={ cartInfo={
activeTab === 'wholesaler' && selectedProducts.length > 0 activeTab === "wholesaler" && selectedProducts.length > 0
? { ? {
itemCount: selectedProducts.length, itemCount: selectedProducts.length,
totalAmount: getTotalAmount(), totalAmount: getTotalAmount(),
formatCurrency formatCurrency,
} }
: undefined : undefined
} }
@ -194,7 +224,8 @@ export function CreateSupplyPage() {
/> />
{/* Контент карточек - новый компонент прямого создания поставки */} {/* Контент карточек - новый компонент прямого создания поставки */}
{activeTab === 'cards' && ( {activeTab === "cards" && (
<div className="flex-1 flex flex-col overflow-hidden min-h-0">
<DirectSupplyCreation <DirectSupplyCreation
onComplete={handleSupplyCompleted} onComplete={handleSupplyCompleted}
onCreateSupply={handleCreateSupplyClick} onCreateSupply={handleCreateSupplyClick}
@ -202,10 +233,11 @@ export function CreateSupplyPage() {
isCreatingSupply={isCreatingSupply} isCreatingSupply={isCreatingSupply}
onCanCreateSupplyChange={handleCanCreateSupplyChange} onCanCreateSupplyChange={handleCanCreateSupplyChange}
/> />
</div>
)} )}
{/* Контент оптовиков */} {/* Контент оптовиков */}
{activeTab === 'wholesaler' && ( {activeTab === "wholesaler" && (
<div> <div>
<CartSummary <CartSummary
selectedProducts={selectedProducts} selectedProducts={selectedProducts}
@ -237,5 +269,5 @@ export function CreateSupplyPage() {
</div> </div>
</main> </main>
</div> </div>
) );
} }

View File

@ -668,14 +668,14 @@ export function DirectSupplyCreation({
return ( return (
<> <>
<style>{lineClampStyles}</style> <style>{lineClampStyles}</style>
<div className="space-y-3 w-full"> <div className="flex flex-col h-full space-y-2 w-full min-h-0">
{/* НОВЫЙ БЛОК СОЗДАНИЯ ПОСТАВКИ */} {/* НОВЫЙ БЛОК СОЗДАНИЯ ПОСТАВКИ */}
<Card className="bg-white/10 backdrop-blur-xl border border-white/20 p-3"> <Card className="bg-white/10 backdrop-blur-xl border border-white/20 p-2">
{/* Первая строка */} {/* Первая строка */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-2 items-end mb-0.5"> <div className="grid grid-cols-1 md:grid-cols-4 gap-1.5 items-end mb-0.5">
{/* 1. Модуль выбора даты */} {/* 1. Модуль выбора даты */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block flex items-center gap-1"> <Label className="text-white/80 text-xs mb-0.5 block flex items-center gap-1">
<CalendarIcon className="h-3 w-3" /> <CalendarIcon className="h-3 w-3" />
Дата Дата
</Label> </Label>
@ -684,7 +684,7 @@ export function DirectSupplyCreation({
type="date" type="date"
value={deliveryDate} value={deliveryDate}
onChange={(e) => setDeliveryDate(e.target.value)} onChange={(e) => setDeliveryDate(e.target.value)}
className="w-full h-8 rounded-lg border-0 bg-white/20 backdrop-blur px-2 py-1 text-white placeholder:text-white/50 focus:bg-white/30 focus:outline-none focus:ring-1 focus:ring-white/20 text-xs font-medium" className="w-full h-7 rounded-lg border-0 bg-white/20 backdrop-blur px-2 py-1 text-white placeholder:text-white/50 focus:bg-white/30 focus:outline-none focus:ring-1 focus:ring-white/20 text-xs font-medium"
min={new Date().toISOString().split("T")[0]} min={new Date().toISOString().split("T")[0]}
/> />
</div> </div>
@ -692,7 +692,7 @@ export function DirectSupplyCreation({
{/* 2. Модуль выбора фулфилмента */} {/* 2. Модуль выбора фулфилмента */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block flex items-center gap-1"> <Label className="text-white/80 text-xs mb-0.5 block flex items-center gap-1">
<Building className="h-3 w-3" /> <Building className="h-3 w-3" />
Фулфилмент Фулфилмент
</Label> </Label>
@ -700,7 +700,7 @@ export function DirectSupplyCreation({
value={selectedFulfillment} value={selectedFulfillment}
onValueChange={setSelectedFulfillment} onValueChange={setSelectedFulfillment}
> >
<SelectTrigger className="w-full h-8 py-0 px-2 bg-white/20 border-0 text-white focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"> <SelectTrigger className="w-full h-7 py-0 px-2 bg-white/20 border-0 text-white focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs">
<SelectValue placeholder="ФУЛФИЛМЕНТ ИВАНОВО" /> <SelectValue placeholder="ФУЛФИЛМЕНТ ИВАНОВО" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -715,7 +715,7 @@ export function DirectSupplyCreation({
{/* 3. Объём товаров */} {/* 3. Объём товаров */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block"> <Label className="text-white/80 text-xs mb-0.5 block">
Объём товаров Объём товаров
</Label> </Label>
<Input <Input
@ -725,13 +725,13 @@ export function DirectSupplyCreation({
setGoodsVolume(parseFloat(e.target.value) || 0) setGoodsVolume(parseFloat(e.target.value) || 0)
} }
placeholder="м³" placeholder="м³"
className="h-8 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs" className="h-7 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"
/> />
</div> </div>
{/* 4. Грузовые места */} {/* 4. Грузовые места */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block"> <Label className="text-white/80 text-xs mb-0.5 block">
Грузовые места Грузовые места
</Label> </Label>
<Input <Input
@ -739,16 +739,16 @@ export function DirectSupplyCreation({
value={cargoPlaces || ""} value={cargoPlaces || ""}
onChange={(e) => setCargoPlaces(parseInt(e.target.value) || 0)} onChange={(e) => setCargoPlaces(parseInt(e.target.value) || 0)}
placeholder="шт" placeholder="шт"
className="h-8 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs" className="h-7 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"
/> />
</div> </div>
</div> </div>
{/* Вторая строка */} {/* Вторая строка */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-2 items-end"> <div className="grid grid-cols-1 md:grid-cols-4 gap-1.5 items-end">
{/* 5. Цена товаров */} {/* 5. Цена товаров */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block"> <Label className="text-white/80 text-xs mb-0.5 block">
Цена товаров Цена товаров
</Label> </Label>
<Input <Input
@ -756,13 +756,13 @@ export function DirectSupplyCreation({
value={goodsPrice || ""} value={goodsPrice || ""}
onChange={(e) => setGoodsPrice(parseFloat(e.target.value) || 0)} onChange={(e) => setGoodsPrice(parseFloat(e.target.value) || 0)}
placeholder="₽" placeholder="₽"
className="h-8 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs" className="h-7 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"
/> />
</div> </div>
{/* 6. Цена услуг фулфилмента */} {/* 6. Цена услуг фулфилмента */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block"> <Label className="text-white/80 text-xs mb-0.5 block">
Цена услуг фулфилмент Цена услуг фулфилмент
</Label> </Label>
<Input <Input
@ -772,13 +772,13 @@ export function DirectSupplyCreation({
setFulfillmentServicesPrice(parseFloat(e.target.value) || 0) setFulfillmentServicesPrice(parseFloat(e.target.value) || 0)
} }
placeholder="₽" placeholder="₽"
className="h-8 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs" className="h-7 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"
/> />
</div> </div>
{/* 7. Цена логистики */} {/* 7. Цена логистики */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block"> <Label className="text-white/80 text-xs mb-0.5 block">
Логистика до фулфилмента Логистика до фулфилмента
</Label> </Label>
<Input <Input
@ -788,14 +788,16 @@ export function DirectSupplyCreation({
setLogisticsPrice(parseFloat(e.target.value) || 0) setLogisticsPrice(parseFloat(e.target.value) || 0)
} }
placeholder="₽" placeholder="₽"
className="h-8 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs" className="h-7 bg-white/20 border-0 text-white placeholder:text-white/50 focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs"
/> />
</div> </div>
{/* 8. Итоговая сумма */} {/* 8. Итоговая сумма */}
<div> <div>
<Label className="text-white/80 text-xs mb-1 block">Итого</Label> <Label className="text-white/80 text-xs mb-0.5 block">
<div className="h-8 bg-white/10 rounded-lg flex items-center justify-center"> Итого
</Label>
<div className="h-7 bg-white/10 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm"> <span className="text-white font-bold text-sm">
{formatCurrency(getTotalSum()).replace(" ₽", " ₽")} {formatCurrency(getTotalSum()).replace(" ₽", " ₽")}
</span> </span>
@ -804,143 +806,187 @@ export function DirectSupplyCreation({
</div> </div>
</Card> </Card>
{/* Блок поиска товаров - оптимизированное расположение */} {/* Элегантный блок поиска и товаров */}
<Card className="bg-white/10 backdrop-blur-xl border border-white/20 p-3"> <div className="relative">
<div className="mb-1"> {/* Главная карточка с градиентом */}
<Label className="text-white/80 text-xs mb-2 block flex items-center gap-1"> <div className="bg-gradient-to-br from-white/15 via-white/10 to-white/5 backdrop-blur-xl border border-white/20 rounded-2xl p-4 shadow-2xl">
<Search className="h-3 w-3" /> {/* Компактный заголовок с поиском */}
Поиск товаров Wildberries <div className="flex items-center justify-between mb-2">
</Label> <div className="flex items-center space-x-3">
<div className="flex items-center space-x-2"> <div className="w-8 h-8 bg-gradient-to-r from-purple-500 to-blue-500 rounded-lg flex items-center justify-center shadow-lg">
<Search className="h-4 w-4 text-white" />
</div>
<div>
<h3 className="text-white font-semibold text-base">
Каталог товаров
</h3>
<p className="text-white/60 text-xs">
Найдено: {wbCards.length}
</p>
</div>
</div>
{/* Поиск в заголовке */}
<div className="flex items-center space-x-3 flex-1 max-w-md ml-4">
<div className="relative flex-1">
<Input <Input
placeholder="Введите название товара, артикул или бренд..." placeholder="Поиск товаров..."
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="bg-white/20 border-0 text-white placeholder-white/60 h-8 text-xs flex-1 focus:bg-white/30 focus:ring-1 focus:ring-white/20" className="pl-3 pr-16 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:bg-white/15 focus:border-white/40 text-sm h-8"
onKeyPress={(e) => e.key === "Enter" && searchCards()} onKeyPress={(e) => e.key === "Enter" && searchCards()}
/> />
<Button <Button
onClick={searchCards} onClick={searchCards}
disabled={loading} disabled={loading}
className="h-8 px-4 bg-white/20 hover:bg-white/30 border-0 text-white text-xs font-medium backdrop-blur" className="absolute right-1 top-1 h-6 px-2 bg-gradient-to-r from-purple-500 to-blue-500 hover:from-purple-600 hover:to-blue-600 text-white border-0 rounded text-xs"
> >
{loading ? ( {loading ? (
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-white"></div> <div className="animate-spin rounded-full h-3 w-3 border border-white/30 border-t-white"></div>
) : ( ) : (
<> "Найти"
<Search className="h-3 w-3 mr-1" />
Поиск
</>
)} )}
</Button> </Button>
</div> </div>
</div> </div>
{/* Карточки товаров - увеличенный размер и единообразность */} {/* Статистика в поставке */}
<div className="space-y-1">
<div className="flex items-center justify-between">
<span className="text-white/80 text-xs font-medium">
Найдено товаров: {wbCards.length}
</span>
{supplyItems.length > 0 && ( {supplyItems.length > 0 && (
<Badge <div className="bg-gradient-to-r from-purple-500/20 to-blue-500/20 backdrop-blur border border-purple-400/30 rounded-lg px-3 py-1 ml-3">
variant="secondary" <div className="flex items-center space-x-2">
className="bg-purple-500/20 text-purple-200 text-xs" <div className="w-1.5 h-1.5 bg-purple-400 rounded-full animate-pulse"></div>
> <span className="text-purple-200 font-medium text-xs">
В поставке: {supplyItems.length} В поставке: {supplyItems.length}
</Badge> </span>
</div>
</div>
)} )}
</div> </div>
<div className="grid grid-cols-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8 xl:grid-cols-10 gap-2"> {/* Сетка товаров */}
{loading <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-3">
? [...Array(12)].map((_, i) => ( {loading ? (
<div // Красивые skeleton-карточки
key={i} [...Array(16)].map((_, i) => (
className="aspect-[3/4] bg-white/5 rounded-lg animate-pulse" <div key={i} className="group">
></div> <div className="aspect-[3/4] bg-gradient-to-br from-white/10 to-white/5 rounded-xl animate-pulse">
<div className="w-full h-full bg-white/5 rounded-xl"></div>
</div>
<div className="mt-1 px-1">
<div className="h-3 bg-white/10 rounded animate-pulse"></div>
</div>
</div>
)) ))
: wbCards.map((card) => { ) : wbCards.length > 0 ? (
// Красивые карточки товаров
wbCards.map((card) => {
const isInSupply = supplyItems.some( const isInSupply = supplyItems.some(
(item) => item.card.nmID === card.nmID (item) => item.card.nmID === card.nmID
); );
return ( return (
<div <div
key={card.nmID} key={card.nmID}
className={`group relative cursor-pointer transition-all duration-200 hover:scale-105 hover:z-10 ${ className={`group cursor-pointer transition-all duration-300 hover:scale-105 ${
isInSupply isInSupply ? "scale-105" : ""
? "ring-2 ring-purple-400 ring-offset-1 ring-offset-transparent"
: ""
}`} }`}
onClick={() => addToSupply(card)} onClick={() => addToSupply(card)}
> >
<div className="aspect-[3/4] bg-white/10 rounded-lg overflow-hidden backdrop-blur-sm border border-white/20 hover:border-white/40 transition-all"> {/* Карточка товара */}
<div
className={`relative aspect-[3/4] rounded-xl overflow-hidden shadow-lg transition-all duration-300 ${
isInSupply
? "ring-2 ring-purple-400 shadow-purple-400/25 bg-gradient-to-br from-purple-500/20 to-blue-500/20"
: "bg-white/10 hover:bg-white/15 hover:shadow-xl"
}`}
>
<img <img
src={ src={
WildberriesService.getCardImage( WildberriesService.getCardImage(card, "c516x688") ||
card, "/api/placeholder/200/267"
"c516x688"
) || "/api/placeholder/120/160"
} }
alt={card.title} alt={card.title}
className="w-full h-full object-cover transition-transform group-hover:scale-110" className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
loading="lazy" loading="lazy"
/> />
{/* Оверлей с информацией */} {/* Градиентный оверлей */}
<div className="absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity"> <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300" />
<div className="absolute bottom-2 left-2 right-2">
<div className="text-white text-xs font-medium line-clamp-2 mb-1"> {/* Информация при наведении */}
<div className="absolute bottom-0 left-0 right-0 p-3 transform translate-y-full group-hover:translate-y-0 transition-transform duration-300">
<h4 className="text-white font-medium text-sm line-clamp-2 mb-1">
{card.title} {card.title}
</div> </h4>
<div className="text-white/70 text-[10px]"> <p className="text-white/80 text-xs mb-1">
Арт: {card.vendorCode} Арт: {card.vendorCode}
</div> </p>
{card.sizes && card.sizes[0] && ( {card.sizes && card.sizes[0] && (
<div className="text-purple-300 text-[10px] font-medium"> <p className="text-purple-300 font-semibold text-sm">
от{" "} от{" "}
{card.sizes[0].discountedPrice || {card.sizes[0].discountedPrice ||
card.sizes[0].price}{" "} card.sizes[0].price}{" "}
</div> </p>
)} )}
</div> </div>
</div>
{/* Индикатор добавления в поставку */} {/* Индикаторы */}
{isInSupply && ( {isInSupply ? (
<div className="absolute top-2 right-2 bg-purple-500 text-white rounded-full w-6 h-6 flex items-center justify-center text-xs font-bold shadow-lg"> <div className="absolute top-3 right-3 w-8 h-8 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full flex items-center justify-center shadow-lg">
<svg
className="w-4 h-4 text-white"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fillRule="evenodd"
d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
clipRule="evenodd"
/>
</svg>
</div>
) : (
<div className="absolute top-3 right-3 w-8 h-8 bg-white/20 backdrop-blur rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
<Plus className="w-4 h-4 text-white" />
</div> </div>
)} )}
{/* Индикатор при наведении */} {/* Эффект при клике */}
<div className="absolute top-2 left-2 bg-white/20 backdrop-blur text-white rounded-full w-6 h-6 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"> <div className="absolute inset-0 bg-white/20 opacity-0 group-active:opacity-100 transition-opacity duration-150" />
<Plus className="h-3 w-3" />
</div> </div>
{/* Название под карточкой */}
<div className="mt-1 px-1">
<h4 className="text-white/90 font-medium text-xs line-clamp-2 leading-tight">
{card.title}
</h4>
</div> </div>
</div> </div>
); );
})} })
) : (
// Пустое состояние
<div className="col-span-full flex flex-col items-center justify-center py-8">
<div className="w-16 h-16 bg-gradient-to-r from-purple-500/20 to-blue-500/20 rounded-2xl flex items-center justify-center mb-3">
<Package className="w-8 h-8 text-white/40" />
</div>
<h3 className="text-white/80 font-medium text-base mb-1">
{searchTerm ? "Товары не найдены" : "Начните поиск товаров"}
</h3>
<p className="text-white/50 text-sm text-center max-w-md">
{searchTerm
? "Попробуйте изменить поисковый запрос"
: "Введите название товара в поле поиска"}
</p>
</div>
)}
</div>
</div> </div>
{!loading && wbCards.length === 0 && ( {/* Декоративные элементы */}
<div className="text-center py-4"> <div className="absolute -top-1 -left-1 w-4 h-4 bg-gradient-to-r from-purple-500 to-blue-500 rounded-full opacity-60 animate-pulse" />
<Package className="h-8 w-8 text-white/20 mx-auto mb-2" /> <div className="absolute -bottom-1 -right-1 w-3 h-3 bg-gradient-to-r from-blue-500 to-purple-500 rounded-full opacity-40 animate-pulse delay-700" />
<p className="text-white/60 text-xs">
{searchTerm
? "Товары не найдены"
: "Введите запрос для поиска товаров"}
</p>
{searchTerm && (
<p className="text-white/40 text-[10px] mt-1">
Попробуйте изменить условия поиска
</p>
)}
</div> </div>
)}
</div>
</Card>
{/* Услуги и расходники в одной строке */} {/* Услуги и расходники в одной строке */}
{selectedFulfillmentOrg && ( {selectedFulfillmentOrg && (
@ -1027,44 +1073,37 @@ export function DirectSupplyCreation({
</Card> </Card>
)} )}
{/* Модуль товаров в поставке - новый дизайн */} {/* Модуль товаров в поставке - растягивается до низа */}
<Card className="bg-white/10 backdrop-blur border-white/20 p-3"> <Card className="bg-white/10 backdrop-blur border-white/20 p-2 flex-1 flex flex-col min-h-0">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-2 flex-shrink-0">
<span className="text-white font-medium text-sm"> <span className="text-white font-medium text-sm">
Товары в поставке Товары в поставке
</span> </span>
<Button
onClick={() => setShowSupplierModal(true)}
variant="outline"
size="sm"
className="bg-white/5 border-white/20 text-white hover:bg-white/10 h-7 px-3 text-xs"
>
<Plus className="h-3 w-3 mr-1" />
Поставщик
</Button>
</div> </div>
{supplyItems.length === 0 ? ( {supplyItems.length === 0 ? (
<div className="text-center py-6"> <div className="flex-1 flex items-center justify-center">
<div className="text-center">
<Package className="h-8 w-8 text-white/20 mx-auto mb-2" /> <Package className="h-8 w-8 text-white/20 mx-auto mb-2" />
<p className="text-white/60 text-xs"> <p className="text-white/60 text-xs">
Добавьте товары из карточек выше Добавьте товары из карточек выше
</p> </p>
</div> </div>
</div>
) : ( ) : (
<div className="space-y-4"> <div className="flex-1 overflow-y-auto space-y-1">
{supplyItems.map((item) => ( {supplyItems.map((item) => (
<Card <Card
key={item.card.nmID} key={item.card.nmID}
className="bg-white/5 border-white/10 p-3" className="bg-white/5 border-white/10 p-1.5"
> >
{/* Заголовок товара с кнопкой удаления */} {/* Компактный заголовок товара */}
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-1">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2 min-w-0">
<div className="text-white font-medium text-sm line-clamp-1"> <div className="text-white font-medium text-xs line-clamp-1 truncate">
{item.card.title} {item.card.title}
</div> </div>
<div className="text-white/60 text-xs"> <div className="text-white/60 text-[10px] flex-shrink-0">
Арт: {item.card.vendorCode} Арт: {item.card.vendorCode}
</div> </div>
</div> </div>
@ -1072,47 +1111,44 @@ export function DirectSupplyCreation({
onClick={() => removeFromSupply(item.card.nmID)} onClick={() => removeFromSupply(item.card.nmID)}
size="sm" size="sm"
variant="ghost" variant="ghost"
className="h-6 w-6 p-0 text-white/60 hover:text-red-400" className="h-5 w-5 p-0 text-white/60 hover:text-red-400 flex-shrink-0"
> >
<X className="h-4 w-4" /> <X className="h-3 w-3" />
</Button> </Button>
</div> </div>
{/* Названия блоков */} {/* Компактные названия блоков */}
<div <div className="grid grid-cols-8 gap-1 mb-1">
className="grid grid-cols-8 gap-2" <div className="text-white/80 text-[9px] font-medium text-center">
style={{ marginBottom: "4px" }}
>
<div className="text-white/80 text-xs font-medium text-center">
Товар Товар
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Параметры Параметры
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Заказать Заказать
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Цена Цена
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Услуги фулфилмента Услуги фф
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Поставщик Поставщик
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Расходники фулфилмента Расходники фф
</div> </div>
<div className="text-white/80 text-xs font-medium text-center"> <div className="text-white/80 text-[9px] font-medium text-center">
Расходники селлера Расходники
</div> </div>
</div> </div>
{/* Оптимизированная сетка для 13" - все блоки в одну строку */} {/* Компактная сетка блоков */}
<div className="grid grid-cols-8 gap-2"> <div className="grid grid-cols-8 gap-1">
{/* Блок 1: Картинка товара */} {/* Блок 1: Картинка товара */}
<div className="bg-white/10 rounded-lg overflow-hidden relative"> <div className="bg-white/10 rounded-lg overflow-hidden relative h-20">
<img <img
src={ src={
WildberriesService.getCardImage( WildberriesService.getCardImage(
@ -1126,16 +1162,16 @@ export function DirectSupplyCreation({
</div> </div>
{/* Блок 2: Параметры */} {/* Блок 2: Параметры */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center h-20">
<div className="space-y-1"> <div className="space-y-1">
<div className="text-white/70 text-[9px] text-center"> <div className="text-white/70 text-xs text-center">
{item.card.object} {item.card.object}
</div> </div>
<div className="text-white/70 text-[9px] text-center"> <div className="text-white/70 text-xs text-center">
{item.card.countryProduction} {item.card.countryProduction}
</div> </div>
{item.card.sizes && item.card.sizes[0] && ( {item.card.sizes && item.card.sizes[0] && (
<div className="text-white/70 text-[9px] text-center"> <div className="text-white/70 text-xs text-center">
{item.card.sizes[0].techSize} {item.card.sizes[0].techSize}
</div> </div>
)} )}
@ -1143,8 +1179,8 @@ export function DirectSupplyCreation({
</div> </div>
{/* Блок 3: Заказать */} {/* Блок 3: Заказать */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center h-20">
<div className="text-white/60 text-[10px] mb-1 text-center"> <div className="text-white/60 text-xs mb-2 text-center">
Количество Количество
</div> </div>
<Input <Input
@ -1157,14 +1193,14 @@ export function DirectSupplyCreation({
parseInt(e.target.value) || 0 parseInt(e.target.value) || 0
) )
} }
className="bg-purple-500/20 border-purple-400/30 text-white text-center h-7 text-xs font-bold" className="bg-purple-500/20 border-purple-400/30 text-white text-center h-8 text-sm font-bold"
min="1" min="1"
/> />
</div> </div>
{/* Блок 4: Цена */} {/* Блок 4: Цена */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center h-20">
<div className="text-white/60 text-[10px] mb-1 text-center"> <div className="text-white/60 text-xs mb-1 text-center">
За единицу За единицу
</div> </div>
<Input <Input
@ -1180,22 +1216,22 @@ export function DirectSupplyCreation({
className="bg-white/20 border-white/20 text-white text-center h-7 text-xs" className="bg-white/20 border-white/20 text-white text-center h-7 text-xs"
placeholder="₽" placeholder="₽"
/> />
<div className="text-white/80 text-[9px] font-medium text-center mt-1"> <div className="text-white/80 text-xs font-medium text-center mt-1">
{formatCurrency(item.totalPrice).replace(" ₽", "₽")} {formatCurrency(item.totalPrice).replace(" ₽", "₽")}
</div> </div>
</div> </div>
{/* Блок 5: Услуги фулфилмента */} {/* Блок 5: Услуги фулфилмента */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center h-20">
<div className="space-y-1 max-h-16 overflow-y-auto"> <div className="space-y-2 max-h-16 overflow-y-auto">
{selectedFulfillmentOrg && {selectedFulfillmentOrg &&
organizationServices[selectedFulfillmentOrg] ? ( organizationServices[selectedFulfillmentOrg] ? (
organizationServices[selectedFulfillmentOrg] organizationServices[selectedFulfillmentOrg]
.slice(0, 2) .slice(0, 4)
.map((service) => ( .map((service) => (
<label <label
key={service.id} key={service.id}
className="flex items-center space-x-1 cursor-pointer" className="flex items-center space-x-2 cursor-pointer"
> >
<input <input
type="checkbox" type="checkbox"
@ -1214,15 +1250,15 @@ export function DirectSupplyCreation({
); );
} }
}} }}
className="w-2 h-2" className="w-3 h-3"
/> />
<span className="text-white text-[9px]"> <span className="text-white text-xs">
{service.name.substring(0, 8)}... {service.name.substring(0, 8)}...
</span> </span>
</label> </label>
)) ))
) : ( ) : (
<span className="text-white/60 text-[9px] text-center"> <span className="text-white/60 text-xs text-center">
Выберите фулфилмент Выберите фулфилмент
</span> </span>
)} )}
@ -1230,14 +1266,14 @@ export function DirectSupplyCreation({
</div> </div>
{/* Блок 6: Поставщик */} {/* Блок 6: Поставщик */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center space-y-2 h-20">
<Select <Select
value={item.supplierId} value={item.supplierId}
onValueChange={(value) => onValueChange={(value) =>
updateSupplyItem(item.card.nmID, "supplierId", value) updateSupplyItem(item.card.nmID, "supplierId", value)
} }
> >
<SelectTrigger className="bg-white/20 border-white/20 text-white h-7 text-[10px]"> <SelectTrigger className="bg-white/20 border-white/20 text-white h-7 text-xs">
<SelectValue placeholder="Выбрать" /> <SelectValue placeholder="Выбрать" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
@ -1248,19 +1284,49 @@ export function DirectSupplyCreation({
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{/* Информация о выбранном поставщике */}
{item.supplierId &&
suppliers.find((s) => s.id === item.supplierId) && (
<div className="text-xs text-white/60 space-y-1">
<div className="truncate">
{
suppliers.find((s) => s.id === item.supplierId)
?.contactName
}
</div>
<div className="truncate">
{
suppliers.find((s) => s.id === item.supplierId)
?.phone
}
</div>
</div>
)}
{/* Кнопка добавления поставщика */}
<Button
onClick={() => setShowSupplierModal(true)}
variant="outline"
size="sm"
className="bg-white/5 border-white/20 text-white hover:bg-white/10 h-6 px-2 text-xs w-full"
>
<Plus className="h-3 w-3 mr-1" />
Добавить
</Button>
</div> </div>
{/* Блок 7: Расходники фулфилмента */} {/* Блок 7: Расходники фф */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center h-20">
<div className="space-y-1 max-h-16 overflow-y-auto"> <div className="space-y-2 max-h-16 overflow-y-auto">
{selectedFulfillmentOrg && {selectedFulfillmentOrg &&
organizationSupplies[selectedFulfillmentOrg] ? ( organizationSupplies[selectedFulfillmentOrg] ? (
organizationSupplies[selectedFulfillmentOrg] organizationSupplies[selectedFulfillmentOrg]
.slice(0, 2) .slice(0, 4)
.map((supply) => ( .map((supply) => (
<label <label
key={supply.id} key={supply.id}
className="flex items-center space-x-1 cursor-pointer" className="flex items-center space-x-2 cursor-pointer"
> >
<input <input
type="checkbox" type="checkbox"
@ -1279,15 +1345,15 @@ export function DirectSupplyCreation({
); );
} }
}} }}
className="w-2 h-2" className="w-3 h-3"
/> />
<span className="text-white text-[9px]"> <span className="text-white text-xs">
{supply.name.substring(0, 6)}... {supply.name.substring(0, 6)}...
</span> </span>
</label> </label>
)) ))
) : ( ) : (
<span className="text-white/60 text-[9px] text-center"> <span className="text-white/60 text-xs text-center">
Выберите фулфилмент Выберите фулфилмент
</span> </span>
)} )}
@ -1295,23 +1361,19 @@ export function DirectSupplyCreation({
</div> </div>
{/* Блок 8: Расходники селлера */} {/* Блок 8: Расходники селлера */}
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center"> <div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center h-20">
<div className="space-y-1"> <div className="space-y-2">
<label className="flex items-center space-x-1 cursor-pointer"> <label className="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" className="w-2 h-2" /> <input type="checkbox" className="w-3 h-3" />
<span className="text-white text-[9px]"> <span className="text-white text-xs">Упаковка</span>
Упаковка
</span>
</label> </label>
<label className="flex items-center space-x-1 cursor-pointer"> <label className="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" className="w-2 h-2" /> <input type="checkbox" className="w-3 h-3" />
<span className="text-white text-[9px]"> <span className="text-white text-xs">Этикетки</span>
Этикетки
</span>
</label> </label>
<label className="flex items-center space-x-1 cursor-pointer"> <label className="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" className="w-2 h-2" /> <input type="checkbox" className="w-3 h-3" />
<span className="text-white text-[9px]">Пакеты</span> <span className="text-white text-xs">Пакеты</span>
</label> </label>
</div> </div>
</div> </div>