Добавлена логика маршрутизации для поставок в зависимости от типа организации пользователя. Обновлены компоненты боковой панели и страницы создания поставки: реализован поиск оптовиков, улучшена фильтрация товаров и адаптация данных оптовиков. Убраны неиспользуемые поля и улучшен интерфейс отображения информации о товарах и оптовиках.
This commit is contained in:
@ -1,11 +1,14 @@
|
||||
"use client"
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useQuery } 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 { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { GET_MY_COUNTERPARTIES, GET_ALL_PRODUCTS } from '@/graphql/queries'
|
||||
import {
|
||||
ArrowLeft,
|
||||
ShoppingCart,
|
||||
@ -22,7 +25,8 @@ import {
|
||||
Zap,
|
||||
Heart,
|
||||
Eye,
|
||||
ShoppingBag
|
||||
ShoppingBag,
|
||||
Search
|
||||
} from 'lucide-react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import Image from 'next/image'
|
||||
@ -221,10 +225,38 @@ const mockProducts: WholesalerProduct[] = [
|
||||
|
||||
export function CreateSupplyPage() {
|
||||
const router = useRouter()
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const [selectedVariant, setSelectedVariant] = useState<'cards' | 'wholesaler' | null>(null)
|
||||
const [selectedWholesaler, setSelectedWholesaler] = useState<WholesalerForCreation | null>(null)
|
||||
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>([])
|
||||
const [showSummary, setShowSummary] = useState(false)
|
||||
const [searchQuery, setSearchQuery] = useState('')
|
||||
|
||||
// Загружаем контрагентов-оптовиков
|
||||
const { data: counterpartiesData, loading: counterpartiesLoading } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
|
||||
// Загружаем товары для выбранного оптовика
|
||||
const { data: productsData, loading: productsLoading } = useQuery(GET_ALL_PRODUCTS, {
|
||||
skip: !selectedWholesaler,
|
||||
variables: { search: null, category: null }
|
||||
})
|
||||
|
||||
// Фильтруем только оптовиков
|
||||
const wholesalers = (counterpartiesData?.myCounterparties || []).filter((org: { type: string }) => org.type === 'WHOLESALE')
|
||||
|
||||
// Фильтруем оптовиков по поисковому запросу
|
||||
const filteredWholesalers = wholesalers.filter((wholesaler: { name?: string; fullName?: string; inn?: string }) =>
|
||||
wholesaler.name?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
wholesaler.fullName?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
wholesaler.inn?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
)
|
||||
|
||||
// Фильтруем товары по выбранному оптовику
|
||||
const wholesalerProducts = selectedWholesaler
|
||||
? (productsData?.allProducts || []).filter((product: { organization: { id: string } }) =>
|
||||
product.organization.id === selectedWholesaler.id
|
||||
)
|
||||
: []
|
||||
|
||||
// Автоматически показываем корзину если в ней есть товары и мы на этапе выбора оптовиков
|
||||
useEffect(() => {
|
||||
@ -251,7 +283,7 @@ export function CreateSupplyPage() {
|
||||
}
|
||||
|
||||
const updateProductQuantity = (productId: string, quantity: number) => {
|
||||
const product = mockProducts.find(p => p.id === productId)
|
||||
const product = wholesalerProducts.find((p: { id: string }) => p.id === productId)
|
||||
if (!product || !selectedWholesaler) return
|
||||
|
||||
setSelectedProducts(prev => {
|
||||
@ -320,9 +352,9 @@ export function CreateSupplyPage() {
|
||||
// Рендер товаров оптовика
|
||||
if (selectedWholesaler && selectedVariant === 'wholesaler') {
|
||||
return (
|
||||
<div className="min-h-screen flex">
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="p-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center space-x-4">
|
||||
@ -337,7 +369,7 @@ export function CreateSupplyPage() {
|
||||
</Button>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-white mb-2">Товары оптовика</h1>
|
||||
<p className="text-white/60">{selectedWholesaler.name} • {mockProducts.length} товаров</p>
|
||||
<p className="text-white/60">{selectedWholesaler.name} • {wholesalerProducts.length} товаров</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
@ -496,12 +528,39 @@ export function CreateSupplyPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
||||
{mockProducts.map((product) => {
|
||||
{productsLoading ? (
|
||||
<div className="flex items-center justify-center p-8 col-span-full">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-4 border-white border-t-transparent mx-auto mb-4"></div>
|
||||
<p className="text-white/60">Загружаем товары...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : wholesalerProducts.length === 0 ? (
|
||||
<div className="flex items-center justify-center p-8 col-span-full">
|
||||
<div className="text-center">
|
||||
<Package className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
||||
<p className="text-white/60">У этого оптовика нет товаров</p>
|
||||
<p className="text-white/40 text-sm mt-2">Выберите другого оптовика</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4">
|
||||
{wholesalerProducts.map((product: {
|
||||
id: string;
|
||||
name: string;
|
||||
article: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
quantity: number;
|
||||
category?: { name: string };
|
||||
brand?: string;
|
||||
color?: string;
|
||||
size?: string;
|
||||
mainImage?: string;
|
||||
images?: string[]
|
||||
}) => {
|
||||
const selectedQuantity = getSelectedQuantity(product.id)
|
||||
const discountedPrice = product.discount
|
||||
? product.price * (1 - product.discount / 100)
|
||||
: product.price
|
||||
const discountedPrice = product.price // Убираем discount так как его нет в схеме
|
||||
|
||||
return (
|
||||
<Card key={product.id} className="bg-white/10 backdrop-blur border-white/20 overflow-hidden group hover:bg-white/15 hover:border-white/30 transition-all duration-300 hover:scale-105 hover:shadow-2xl">
|
||||
@ -519,14 +578,7 @@ export function CreateSupplyPage() {
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{/* Discount badge */}
|
||||
{product.discount && (
|
||||
<div className="absolute top-2 left-2">
|
||||
<Badge className="bg-red-500 text-white border-0 font-bold text-xs">
|
||||
-{product.discount}%
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
{/* Убираем discount badge так как поля нет в схеме */}
|
||||
|
||||
{/* Overlay с кнопками */}
|
||||
<div className="absolute inset-0 bg-black/40 opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-center justify-center">
|
||||
@ -550,16 +602,7 @@ export function CreateSupplyPage() {
|
||||
{product.brand}
|
||||
</Badge>
|
||||
)}
|
||||
{product.isNew && (
|
||||
<Badge className="bg-blue-500 text-white border-0 text-xs">
|
||||
NEW
|
||||
</Badge>
|
||||
)}
|
||||
{product.isBestseller && (
|
||||
<Badge className="bg-orange-500 text-white border-0 text-xs">
|
||||
ХИТ
|
||||
</Badge>
|
||||
)}
|
||||
{/* Убираем isNew и isBestseller так как этих полей нет в схеме */}
|
||||
</div>
|
||||
<h3 className="text-white font-semibold text-sm mb-1 line-clamp-2 leading-tight">
|
||||
{product.name}
|
||||
@ -578,11 +621,7 @@ export function CreateSupplyPage() {
|
||||
<div className="text-white font-bold text-lg">
|
||||
{formatCurrency(discountedPrice)}
|
||||
</div>
|
||||
{product.discount && (
|
||||
<div className="text-white/40 text-xs line-through">
|
||||
{formatCurrency(product.price)}
|
||||
</div>
|
||||
)}
|
||||
{/* Убираем отображение оригинальной цены так как discount нет */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -633,11 +672,6 @@ export function CreateSupplyPage() {
|
||||
<div className="bg-gradient-to-r from-green-500/20 to-emerald-500/20 border border-green-500/30 rounded p-2">
|
||||
<div className="text-green-300 text-xs font-medium text-center">
|
||||
{formatCurrency(discountedPrice * selectedQuantity)}
|
||||
{product.discount && (
|
||||
<div className="text-green-200 text-xs">
|
||||
экономия {formatCurrency((product.price - discountedPrice) * selectedQuantity)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -646,6 +680,7 @@ export function CreateSupplyPage() {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Floating корзина */}
|
||||
{selectedProducts.length > 0 && (
|
||||
@ -669,9 +704,9 @@ export function CreateSupplyPage() {
|
||||
// Рендер выбора оптовиков
|
||||
if (selectedVariant === 'wholesaler') {
|
||||
return (
|
||||
<div className="min-h-screen flex">
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="p-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center space-x-4">
|
||||
@ -875,12 +910,66 @@ export function CreateSupplyPage() {
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{mockWholesalers.map((wholesaler) => (
|
||||
{/* Поиск */}
|
||||
<div className="mb-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-3 h-4 w-4 text-white/40" />
|
||||
<Input
|
||||
placeholder="Поиск оптовиков по названию или ИНН..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-10 glass-input text-white placeholder:text-white/40"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{counterpartiesLoading ? (
|
||||
<div className="flex items-center justify-center p-8">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-4 border-white border-t-transparent mx-auto mb-4"></div>
|
||||
<p className="text-white/60">Загружаем оптовиков...</p>
|
||||
</div>
|
||||
</div>
|
||||
) : filteredWholesalers.length === 0 ? (
|
||||
<div className="text-center p-8">
|
||||
<Users className="h-12 w-12 text-white/20 mx-auto mb-4" />
|
||||
<p className="text-white/60">
|
||||
{searchQuery ? 'Оптовики не найдены' : 'У вас нет контрагентов-оптовиков'}
|
||||
</p>
|
||||
<p className="text-white/40 text-sm mt-2">
|
||||
{searchQuery ? 'Попробуйте изменить условия поиска' : 'Добавьте оптовиков в разделе "Партнеры"'}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{filteredWholesalers.map((wholesaler: {
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
inn?: string;
|
||||
address?: string;
|
||||
phones?: { value: string }[];
|
||||
emails?: { value: string }[]
|
||||
}) => (
|
||||
<Card
|
||||
key={wholesaler.id}
|
||||
className="bg-white/10 backdrop-blur border-white/20 p-6 cursor-pointer transition-all hover:bg-white/15 hover:border-white/30 hover:scale-105"
|
||||
onClick={() => setSelectedWholesaler(wholesaler)}
|
||||
onClick={() => {
|
||||
// Адаптируем данные под существующий интерфейс
|
||||
const adaptedWholesaler = {
|
||||
id: wholesaler.id,
|
||||
inn: wholesaler.inn || '',
|
||||
name: wholesaler.name || 'Неизвестная организация',
|
||||
fullName: wholesaler.fullName || wholesaler.name || 'Неизвестная организация',
|
||||
address: wholesaler.address || 'Адрес не указан',
|
||||
phone: wholesaler.phones?.[0]?.value,
|
||||
email: wholesaler.emails?.[0]?.value,
|
||||
rating: 4.5, // Временное значение
|
||||
productCount: 0, // Временное значение
|
||||
specialization: ['Оптовая торговля'] // Временное значение
|
||||
}
|
||||
setSelectedWholesaler(adaptedWholesaler)
|
||||
}}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-start space-x-3">
|
||||
@ -889,56 +978,46 @@ export function CreateSupplyPage() {
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-white font-semibold text-lg mb-1 truncate">
|
||||
{wholesaler.name}
|
||||
{wholesaler.name || 'Неизвестная организация'}
|
||||
</h3>
|
||||
<p className="text-white/60 text-xs mb-2 truncate">
|
||||
{wholesaler.fullName}
|
||||
{wholesaler.fullName || wholesaler.name}
|
||||
</p>
|
||||
<div className="flex items-center space-x-1 mb-2">
|
||||
{renderStars(wholesaler.rating)}
|
||||
<span className="text-white/60 text-sm ml-2">{wholesaler.rating}</span>
|
||||
</div>
|
||||
{wholesaler.inn && (
|
||||
<p className="text-white/40 text-xs">
|
||||
ИНН: {wholesaler.inn}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center space-x-2">
|
||||
<MapPin className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-white/80 text-sm truncate">{wholesaler.address}</span>
|
||||
</div>
|
||||
{wholesaler.address && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<MapPin className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-white/80 text-sm truncate">{wholesaler.address}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{wholesaler.phone && (
|
||||
{wholesaler.phones?.[0]?.value && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Phone className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-white/80 text-sm">{wholesaler.phone}</span>
|
||||
<span className="text-white/80 text-sm">{wholesaler.phones[0].value}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{wholesaler.email && (
|
||||
{wholesaler.emails?.[0]?.value && (
|
||||
<div className="flex items-center space-x-2">
|
||||
<Mail className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-white/80 text-sm truncate">{wholesaler.email}</span>
|
||||
<span className="text-white/80 text-sm truncate">{wholesaler.emails[0].value}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Package className="h-4 w-4 text-gray-400" />
|
||||
<span className="text-white/80 text-sm">{wholesaler.productCount} товаров</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<p className="text-white/60 text-xs">Специализация:</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{wholesaler.specialization.map((spec, index) => (
|
||||
<Badge
|
||||
key={index}
|
||||
className="bg-purple-500/20 text-purple-300 border-purple-500/30 text-xs"
|
||||
>
|
||||
{spec}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<Badge className="bg-green-500/20 text-green-300 border-green-500/30">
|
||||
Контрагент
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="pt-2 border-t border-white/10">
|
||||
@ -948,6 +1027,7 @@ export function CreateSupplyPage() {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Floating корзина */}
|
||||
{selectedProducts.length > 0 && !showSummary && (
|
||||
@ -970,9 +1050,9 @@ export function CreateSupplyPage() {
|
||||
|
||||
// Главная страница выбора варианта
|
||||
return (
|
||||
<div className="min-h-screen flex">
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className="flex-1 ml-56">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}>
|
||||
<div className="p-8">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div className="flex items-center space-x-4">
|
||||
|
Reference in New Issue
Block a user