Обновлен компонент DirectSupplyCreation: добавлены новые состояния для управления данными о поставках, улучшен интерфейс для выбора услуг и расходников. Оптимизирована логика обработки товаров в поставке, добавлены новые функции для расчета итоговой стоимости и управления поставщиками. Обновлены стили и структура кода для повышения удобства использования.
This commit is contained in:
@ -172,8 +172,8 @@ export function CreateSupplyPage() {
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
<Sidebar />
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-hidden transition-all duration-300`}>
|
||||
<div className="p-4">
|
||||
<main className={`flex-1 ${getSidebarMargin()} px-4 py-3 overflow-y-auto transition-all duration-300`}>
|
||||
<div className="p-4 min-h-full">
|
||||
<TabsHeader
|
||||
activeTab={activeTab}
|
||||
onTabChange={setActiveTab}
|
||||
|
@ -1,16 +1,31 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import DatePicker from "react-datepicker"
|
||||
import "react-datepicker/dist/react-datepicker.css"
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import DatePicker from "react-datepicker";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import {
|
||||
Search,
|
||||
Plus,
|
||||
@ -20,332 +35,452 @@ import {
|
||||
X,
|
||||
User,
|
||||
Phone,
|
||||
MapPin
|
||||
} from 'lucide-react'
|
||||
import { WildberriesService } from '@/services/wildberries-service'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { useQuery, useMutation } from '@apollo/client'
|
||||
import { apolloClient } from '@/lib/apollo-client'
|
||||
import { GET_MY_COUNTERPARTIES, GET_COUNTERPARTY_SERVICES, GET_COUNTERPARTY_SUPPLIES } from '@/graphql/queries'
|
||||
import { CREATE_WILDBERRIES_SUPPLY } from '@/graphql/mutations'
|
||||
import { toast } from 'sonner'
|
||||
import { format } from 'date-fns'
|
||||
import { ru } from 'date-fns/locale'
|
||||
import { WildberriesCard } from '@/types/supplies'
|
||||
MapPin,
|
||||
Building,
|
||||
Truck,
|
||||
} from "lucide-react";
|
||||
import { WildberriesService } from "@/services/wildberries-service";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { useQuery, useMutation } from "@apollo/client";
|
||||
import { apolloClient } from "@/lib/apollo-client";
|
||||
import {
|
||||
GET_MY_COUNTERPARTIES,
|
||||
GET_COUNTERPARTY_SERVICES,
|
||||
GET_COUNTERPARTY_SUPPLIES,
|
||||
} from "@/graphql/queries";
|
||||
import { CREATE_WILDBERRIES_SUPPLY } from "@/graphql/mutations";
|
||||
import { toast } from "sonner";
|
||||
import { format } from "date-fns";
|
||||
import { ru } from "date-fns/locale";
|
||||
import { WildberriesCard } from "@/types/supplies";
|
||||
|
||||
// Добавляем CSS стили для line-clamp
|
||||
const lineClampStyles = `
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
interface SupplyItem {
|
||||
card: WildberriesCard
|
||||
quantity: number
|
||||
pricePerUnit: number
|
||||
totalPrice: number
|
||||
supplierId: string
|
||||
card: WildberriesCard;
|
||||
quantity: number;
|
||||
pricePerUnit: number;
|
||||
totalPrice: number;
|
||||
supplierId: string;
|
||||
}
|
||||
|
||||
interface Organization {
|
||||
id: string
|
||||
name?: string
|
||||
fullName?: string
|
||||
type: string
|
||||
id: string;
|
||||
name?: string;
|
||||
fullName?: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface FulfillmentService {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
price: number
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
price: number;
|
||||
}
|
||||
|
||||
interface Supplier {
|
||||
id: string
|
||||
name: string
|
||||
contactName: string
|
||||
phone: string
|
||||
market: string
|
||||
address: string
|
||||
place: string
|
||||
telegram: string
|
||||
id: string;
|
||||
name: string;
|
||||
contactName: string;
|
||||
phone: string;
|
||||
market: string;
|
||||
address: string;
|
||||
place: string;
|
||||
telegram: string;
|
||||
}
|
||||
|
||||
interface DirectSupplyCreationProps {
|
||||
onComplete: () => void
|
||||
onCreateSupply: () => void
|
||||
canCreateSupply: boolean
|
||||
isCreatingSupply: boolean
|
||||
onCanCreateSupplyChange?: (canCreate: boolean) => void
|
||||
onComplete: () => void;
|
||||
onCreateSupply: () => void;
|
||||
canCreateSupply: boolean;
|
||||
isCreatingSupply: boolean;
|
||||
onCanCreateSupplyChange?: (canCreate: boolean) => void;
|
||||
}
|
||||
|
||||
export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupply, isCreatingSupply, onCanCreateSupplyChange }: DirectSupplyCreationProps) {
|
||||
const { user } = useAuth()
|
||||
export function DirectSupplyCreation({
|
||||
onComplete,
|
||||
onCreateSupply,
|
||||
canCreateSupply,
|
||||
isCreatingSupply,
|
||||
onCanCreateSupplyChange,
|
||||
}: DirectSupplyCreationProps) {
|
||||
const { user } = useAuth();
|
||||
|
||||
// Состояние для товаров
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [wbCards, setWbCards] = useState<WildberriesCard[]>([])
|
||||
const [supplyItems, setSupplyItems] = useState<SupplyItem[]>([])
|
||||
// Новые состояния для блока создания поставки
|
||||
const [deliveryDate, setDeliveryDate] = useState<string>("");
|
||||
const [selectedFulfillment, setSelectedFulfillment] = useState<string>("");
|
||||
const [goodsQuantity, setGoodsQuantity] = useState<number>(1200);
|
||||
const [goodsVolume, setGoodsVolume] = useState<number>(0);
|
||||
const [cargoPlaces, setCargoPlaces] = useState<number>(0);
|
||||
const [goodsPrice, setGoodsPrice] = useState<number>(0);
|
||||
const [fulfillmentServicesPrice, setFulfillmentServicesPrice] =
|
||||
useState<number>(0);
|
||||
const [logisticsPrice, setLogisticsPrice] = useState<number>(0);
|
||||
|
||||
// Общие настройки
|
||||
const [deliveryDate, setDeliveryDate] = useState<Date | undefined>(undefined)
|
||||
const [selectedFulfillmentOrg, setSelectedFulfillmentOrg] = useState<string>('')
|
||||
const [selectedServices, setSelectedServices] = useState<string[]>([])
|
||||
const [selectedConsumables, setSelectedConsumables] = useState<string[]>([])
|
||||
// Оригинальные состояния для товаров
|
||||
const [searchTerm, setSearchTerm] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [wbCards, setWbCards] = useState<WildberriesCard[]>([]);
|
||||
const [supplyItems, setSupplyItems] = useState<SupplyItem[]>([]);
|
||||
|
||||
// Общие настройки (оригинальные)
|
||||
const [deliveryDateOriginal, setDeliveryDateOriginal] = useState<
|
||||
Date | undefined
|
||||
>(undefined);
|
||||
const [selectedFulfillmentOrg, setSelectedFulfillmentOrg] =
|
||||
useState<string>("");
|
||||
const [selectedServices, setSelectedServices] = useState<string[]>([]);
|
||||
const [selectedConsumables, setSelectedConsumables] = useState<string[]>([]);
|
||||
|
||||
// Поставщики
|
||||
const [suppliers, setSuppliers] = useState<Supplier[]>([])
|
||||
const [showSupplierModal, setShowSupplierModal] = useState(false)
|
||||
const [suppliers, setSuppliers] = useState<Supplier[]>([]);
|
||||
const [showSupplierModal, setShowSupplierModal] = useState(false);
|
||||
const [newSupplier, setNewSupplier] = useState({
|
||||
name: '',
|
||||
contactName: '',
|
||||
phone: '',
|
||||
market: '',
|
||||
address: '',
|
||||
place: '',
|
||||
telegram: ''
|
||||
})
|
||||
name: "",
|
||||
contactName: "",
|
||||
phone: "",
|
||||
market: "",
|
||||
address: "",
|
||||
place: "",
|
||||
telegram: "",
|
||||
});
|
||||
|
||||
// Данные для фулфилмента
|
||||
const [organizationServices, setOrganizationServices] = useState<{[orgId: string]: FulfillmentService[]}>({})
|
||||
const [organizationSupplies, setOrganizationSupplies] = useState<{[orgId: string]: FulfillmentService[]}>({})
|
||||
const [organizationServices, setOrganizationServices] = useState<{
|
||||
[orgId: string]: FulfillmentService[];
|
||||
}>({});
|
||||
const [organizationSupplies, setOrganizationSupplies] = useState<{
|
||||
[orgId: string]: FulfillmentService[];
|
||||
}>({});
|
||||
|
||||
// Загружаем контрагентов-фулфилментов
|
||||
const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES)
|
||||
const { data: counterpartiesData } = useQuery(GET_MY_COUNTERPARTIES);
|
||||
|
||||
// Мутация для создания поставки
|
||||
const [createSupply, { loading: creatingSupply }] = useMutation(CREATE_WILDBERRIES_SUPPLY, {
|
||||
const [createSupply, { loading: creatingSupply }] = useMutation(
|
||||
CREATE_WILDBERRIES_SUPPLY,
|
||||
{
|
||||
onCompleted: (data) => {
|
||||
if (data.createWildberriesSupply.success) {
|
||||
toast.success(data.createWildberriesSupply.message)
|
||||
onComplete()
|
||||
toast.success(data.createWildberriesSupply.message);
|
||||
onComplete();
|
||||
} else {
|
||||
toast.error(data.createWildberriesSupply.message)
|
||||
toast.error(data.createWildberriesSupply.message);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.error('Ошибка при создании поставки')
|
||||
console.error('Error creating supply:', error)
|
||||
toast.error("Ошибка при создании поставки");
|
||||
console.error("Error creating supply:", error);
|
||||
},
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// Моковые данные товаров для демонстрации
|
||||
const getMockCards = (): WildberriesCard[] => [
|
||||
{
|
||||
nmID: 123456789,
|
||||
vendorCode: 'SKU001',
|
||||
title: 'Платье летнее розовое',
|
||||
description: 'Легкое летнее платье из натурального хлопка',
|
||||
brand: 'Fashion',
|
||||
object: 'Платья',
|
||||
parent: 'Одежда',
|
||||
countryProduction: 'Россия',
|
||||
supplierVendorCode: 'SUPPLIER-001',
|
||||
mediaFiles: ['/api/placeholder/400/400'],
|
||||
sizes: [{ chrtID: 123456, techSize: 'M', wbSize: 'M Розовый', price: 2500, discountedPrice: 2000, quantity: 50 }]
|
||||
vendorCode: "SKU001",
|
||||
title: "Платье летнее розовое",
|
||||
description: "Легкое летнее платье из натурального хлопка",
|
||||
brand: "Fashion",
|
||||
object: "Платья",
|
||||
parent: "Одежда",
|
||||
countryProduction: "Россия",
|
||||
supplierVendorCode: "SUPPLIER-001",
|
||||
mediaFiles: ["/api/placeholder/400/400"],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 123456,
|
||||
techSize: "M",
|
||||
wbSize: "M Розовый",
|
||||
price: 2500,
|
||||
discountedPrice: 2000,
|
||||
quantity: 50,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
nmID: 987654321,
|
||||
vendorCode: 'SKU002',
|
||||
title: 'Платье черное вечернее',
|
||||
description: 'Элегантное вечернее платье для особых случаев',
|
||||
brand: 'Fashion',
|
||||
object: 'Платья',
|
||||
parent: 'Одежда',
|
||||
countryProduction: 'Россия',
|
||||
supplierVendorCode: 'SUPPLIER-002',
|
||||
mediaFiles: ['/api/placeholder/400/403'],
|
||||
sizes: [{ chrtID: 987654, techSize: 'M', wbSize: 'M Черный', price: 3500, discountedPrice: 3000, quantity: 30 }]
|
||||
vendorCode: "SKU002",
|
||||
title: "Платье черное вечернее",
|
||||
description: "Элегантное вечернее платье для особых случаев",
|
||||
brand: "Fashion",
|
||||
object: "Платья",
|
||||
parent: "Одежда",
|
||||
countryProduction: "Россия",
|
||||
supplierVendorCode: "SUPPLIER-002",
|
||||
mediaFiles: ["/api/placeholder/400/403"],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 987654,
|
||||
techSize: "M",
|
||||
wbSize: "M Черный",
|
||||
price: 3500,
|
||||
discountedPrice: 3000,
|
||||
quantity: 30,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
nmID: 555666777,
|
||||
vendorCode: 'SKU003',
|
||||
title: 'Блузка белая офисная',
|
||||
description: 'Классическая белая блузка для офиса',
|
||||
brand: 'Office',
|
||||
object: 'Блузки',
|
||||
parent: 'Одежда',
|
||||
countryProduction: 'Турция',
|
||||
supplierVendorCode: 'SUPPLIER-003',
|
||||
mediaFiles: ['/api/placeholder/400/405'],
|
||||
sizes: [{ chrtID: 555666, techSize: 'L', wbSize: 'L Белый', price: 1800, discountedPrice: 1500, quantity: 40 }]
|
||||
vendorCode: "SKU003",
|
||||
title: "Блузка белая офисная",
|
||||
description: "Классическая белая блузка для офиса",
|
||||
brand: "Office",
|
||||
object: "Блузки",
|
||||
parent: "Одежда",
|
||||
countryProduction: "Турция",
|
||||
supplierVendorCode: "SUPPLIER-003",
|
||||
mediaFiles: ["/api/placeholder/400/405"],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 555666,
|
||||
techSize: "L",
|
||||
wbSize: "L Белый",
|
||||
price: 1800,
|
||||
discountedPrice: 1500,
|
||||
quantity: 40,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
nmID: 444333222,
|
||||
vendorCode: 'SKU004',
|
||||
title: 'Джинсы женские синие',
|
||||
description: 'Классические женские джинсы прямого кроя',
|
||||
brand: 'Denim',
|
||||
object: 'Джинсы',
|
||||
parent: 'Одежда',
|
||||
countryProduction: 'Бангладеш',
|
||||
supplierVendorCode: 'SUPPLIER-004',
|
||||
mediaFiles: ['/api/placeholder/400/408'],
|
||||
sizes: [{ chrtID: 444333, techSize: '30', wbSize: '30 Синий', price: 2800, discountedPrice: 2300, quantity: 25 }]
|
||||
vendorCode: "SKU004",
|
||||
title: "Джинсы женские синие",
|
||||
description: "Классические женские джинсы прямого кроя",
|
||||
brand: "Denim",
|
||||
object: "Джинсы",
|
||||
parent: "Одежда",
|
||||
countryProduction: "Бангладеш",
|
||||
supplierVendorCode: "SUPPLIER-004",
|
||||
mediaFiles: ["/api/placeholder/400/408"],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 444333,
|
||||
techSize: "30",
|
||||
wbSize: "30 Синий",
|
||||
price: 2800,
|
||||
discountedPrice: 2300,
|
||||
quantity: 25,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
nmID: 111222333,
|
||||
vendorCode: 'SKU005',
|
||||
title: 'Кроссовки женские белые',
|
||||
description: 'Удобные женские кроссовки для повседневной носки',
|
||||
brand: 'Sport',
|
||||
object: 'Кроссовки',
|
||||
parent: 'Обувь',
|
||||
countryProduction: 'Вьетнам',
|
||||
supplierVendorCode: 'SUPPLIER-005',
|
||||
mediaFiles: ['/api/placeholder/400/410'],
|
||||
sizes: [{ chrtID: 111222, techSize: '37', wbSize: '37 Белый', price: 3200, discountedPrice: 2800, quantity: 35 }]
|
||||
vendorCode: "SKU005",
|
||||
title: "Кроссовки женские белые",
|
||||
description: "Удобные женские кроссовки для повседневной носки",
|
||||
brand: "Sport",
|
||||
object: "Кроссовки",
|
||||
parent: "Обувь",
|
||||
countryProduction: "Вьетнам",
|
||||
supplierVendorCode: "SUPPLIER-005",
|
||||
mediaFiles: ["/api/placeholder/400/410"],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 111222,
|
||||
techSize: "37",
|
||||
wbSize: "37 Белый",
|
||||
price: 3200,
|
||||
discountedPrice: 2800,
|
||||
quantity: 35,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
nmID: 777888999,
|
||||
vendorCode: 'SKU006',
|
||||
title: 'Сумка женская черная',
|
||||
description: 'Стильная женская сумка из экокожи',
|
||||
brand: 'Accessories',
|
||||
object: 'Сумки',
|
||||
parent: 'Аксессуары',
|
||||
countryProduction: 'Китай',
|
||||
supplierVendorCode: 'SUPPLIER-006',
|
||||
mediaFiles: ['/api/placeholder/400/411'],
|
||||
sizes: [{ chrtID: 777888, techSize: 'Универсальный', wbSize: 'Черный', price: 1500, discountedPrice: 1200, quantity: 60 }]
|
||||
}
|
||||
]
|
||||
vendorCode: "SKU006",
|
||||
title: "Сумка женская черная",
|
||||
description: "Стильная женская сумка из экокожи",
|
||||
brand: "Accessories",
|
||||
object: "Сумки",
|
||||
parent: "Аксессуары",
|
||||
countryProduction: "Китай",
|
||||
supplierVendorCode: "SUPPLIER-006",
|
||||
mediaFiles: ["/api/placeholder/400/411"],
|
||||
sizes: [
|
||||
{
|
||||
chrtID: 777888,
|
||||
techSize: "Универсальный",
|
||||
wbSize: "Черный",
|
||||
price: 1500,
|
||||
discountedPrice: 1200,
|
||||
quantity: 60,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
// Загружаем товары при инициализации
|
||||
useEffect(() => {
|
||||
loadCards()
|
||||
}, [user])
|
||||
loadCards();
|
||||
}, [user]);
|
||||
|
||||
const loadCards = async () => {
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
try {
|
||||
const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')
|
||||
const wbApiKey = user?.organization?.apiKeys?.find(
|
||||
(key) => key.marketplace === "WILDBERRIES"
|
||||
);
|
||||
|
||||
if (wbApiKey?.isActive) {
|
||||
const validationData = wbApiKey.validationData as Record<string, string>
|
||||
const apiToken = validationData?.token ||
|
||||
const validationData = wbApiKey.validationData as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
const apiToken =
|
||||
validationData?.token ||
|
||||
validationData?.apiKey ||
|
||||
validationData?.key ||
|
||||
(wbApiKey as { apiKey?: string }).apiKey
|
||||
(wbApiKey as { apiKey?: string }).apiKey;
|
||||
|
||||
if (apiToken) {
|
||||
console.log('Загружаем карточки из WB API...')
|
||||
const cards = await WildberriesService.getAllCards(apiToken, 20)
|
||||
setWbCards(cards)
|
||||
console.log('Загружено карточек из WB API:', cards.length)
|
||||
return
|
||||
console.log("Загружаем карточки из WB API...");
|
||||
const cards = await WildberriesService.getAllCards(apiToken, 20);
|
||||
setWbCards(cards);
|
||||
console.log("Загружено карточек из WB API:", cards.length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Если API ключ не настроен, показываем моковые данные
|
||||
console.log('API ключ WB не настроен, показываем моковые данные')
|
||||
setWbCards(getMockCards())
|
||||
console.log("API ключ WB не настроен, показываем моковые данные");
|
||||
setWbCards(getMockCards());
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки карточек WB:', error)
|
||||
console.error("Ошибка загрузки карточек WB:", error);
|
||||
// При ошибке API показываем моковые данные
|
||||
setWbCards(getMockCards())
|
||||
setWbCards(getMockCards());
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const searchCards = async () => {
|
||||
if (!searchTerm.trim()) {
|
||||
loadCards()
|
||||
return
|
||||
loadCards();
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
setLoading(true);
|
||||
try {
|
||||
const wbApiKey = user?.organization?.apiKeys?.find(key => key.marketplace === 'WILDBERRIES')
|
||||
const wbApiKey = user?.organization?.apiKeys?.find(
|
||||
(key) => key.marketplace === "WILDBERRIES"
|
||||
);
|
||||
|
||||
if (wbApiKey?.isActive) {
|
||||
const validationData = wbApiKey.validationData as Record<string, string>
|
||||
const apiToken = validationData?.token ||
|
||||
const validationData = wbApiKey.validationData as Record<
|
||||
string,
|
||||
string
|
||||
>;
|
||||
const apiToken =
|
||||
validationData?.token ||
|
||||
validationData?.apiKey ||
|
||||
validationData?.key ||
|
||||
(wbApiKey as { apiKey?: string }).apiKey
|
||||
(wbApiKey as { apiKey?: string }).apiKey;
|
||||
|
||||
if (apiToken) {
|
||||
console.log('Поиск в WB API:', searchTerm)
|
||||
const cards = await WildberriesService.searchCards(apiToken, searchTerm, 20)
|
||||
setWbCards(cards)
|
||||
console.log('Найдено карточек в WB API:', cards.length)
|
||||
return
|
||||
console.log("Поиск в WB API:", searchTerm);
|
||||
const cards = await WildberriesService.searchCards(
|
||||
apiToken,
|
||||
searchTerm,
|
||||
20
|
||||
);
|
||||
setWbCards(cards);
|
||||
console.log("Найдено карточек в WB API:", cards.length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Если API ключ не настроен, ищем в моковых данных
|
||||
console.log('API ключ WB не настроен, поиск в моковых данных:', searchTerm)
|
||||
const mockCards = getMockCards()
|
||||
const filteredCards = mockCards.filter(card =>
|
||||
console.log(
|
||||
"API ключ WB не настроен, поиск в моковых данных:",
|
||||
searchTerm
|
||||
);
|
||||
const mockCards = getMockCards();
|
||||
const filteredCards = mockCards.filter(
|
||||
(card) =>
|
||||
card.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.nmID.toString().includes(searchTerm.toLowerCase()) ||
|
||||
card.object?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
setWbCards(filteredCards)
|
||||
console.log('Найдено моковых товаров:', filteredCards.length)
|
||||
);
|
||||
setWbCards(filteredCards);
|
||||
console.log("Найдено моковых товаров:", filteredCards.length);
|
||||
} catch (error) {
|
||||
console.error('Ошибка поиска карточек WB:', error)
|
||||
console.error("Ошибка поиска карточек WB:", error);
|
||||
// При ошибке ищем в моковых данных
|
||||
const mockCards = getMockCards()
|
||||
const filteredCards = mockCards.filter(card =>
|
||||
const mockCards = getMockCards();
|
||||
const filteredCards = mockCards.filter(
|
||||
(card) =>
|
||||
card.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.brand.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
card.nmID.toString().includes(searchTerm.toLowerCase()) ||
|
||||
card.object?.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
)
|
||||
setWbCards(filteredCards)
|
||||
console.log('Найдено моковых товаров (fallback):', filteredCards.length)
|
||||
);
|
||||
setWbCards(filteredCards);
|
||||
console.log("Найдено моковых товаров (fallback):", filteredCards.length);
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Функции для работы с услугами и расходниками
|
||||
const loadOrganizationServices = async (organizationId: string) => {
|
||||
if (organizationServices[organizationId]) return
|
||||
if (organizationServices[organizationId]) return;
|
||||
|
||||
try {
|
||||
const response = await apolloClient.query({
|
||||
query: GET_COUNTERPARTY_SERVICES,
|
||||
variables: { organizationId }
|
||||
})
|
||||
variables: { organizationId },
|
||||
});
|
||||
|
||||
if (response.data?.counterpartyServices) {
|
||||
setOrganizationServices(prev => ({
|
||||
setOrganizationServices((prev) => ({
|
||||
...prev,
|
||||
[organizationId]: response.data.counterpartyServices
|
||||
}))
|
||||
[organizationId]: response.data.counterpartyServices,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки услуг организации:', error)
|
||||
}
|
||||
console.error("Ошибка загрузки услуг организации:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadOrganizationSupplies = async (organizationId: string) => {
|
||||
if (organizationSupplies[organizationId]) return
|
||||
if (organizationSupplies[organizationId]) return;
|
||||
|
||||
try {
|
||||
const response = await apolloClient.query({
|
||||
query: GET_COUNTERPARTY_SUPPLIES,
|
||||
variables: { organizationId }
|
||||
})
|
||||
variables: { organizationId },
|
||||
});
|
||||
|
||||
if (response.data?.counterpartySupplies) {
|
||||
setOrganizationSupplies(prev => ({
|
||||
setOrganizationSupplies((prev) => ({
|
||||
...prev,
|
||||
[organizationId]: response.data.counterpartySupplies
|
||||
}))
|
||||
[organizationId]: response.data.counterpartySupplies,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки расходников организации:', error)
|
||||
}
|
||||
console.error("Ошибка загрузки расходников организации:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Работа с товарами поставки
|
||||
const addToSupply = (card: WildberriesCard) => {
|
||||
const existingItem = supplyItems.find(item => item.card.nmID === card.nmID)
|
||||
const existingItem = supplyItems.find(
|
||||
(item) => item.card.nmID === card.nmID
|
||||
);
|
||||
if (existingItem) {
|
||||
toast.info('Товар уже добавлен в поставку')
|
||||
return
|
||||
toast.info("Товар уже добавлен в поставку");
|
||||
return;
|
||||
}
|
||||
|
||||
const newItem: SupplyItem = {
|
||||
@ -353,114 +488,132 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
quantity: 1200,
|
||||
pricePerUnit: 0,
|
||||
totalPrice: 0,
|
||||
supplierId: ''
|
||||
}
|
||||
supplierId: "",
|
||||
};
|
||||
|
||||
setSupplyItems(prev => [...prev, newItem])
|
||||
toast.success('Товар добавлен в поставку')
|
||||
}
|
||||
setSupplyItems((prev) => [...prev, newItem]);
|
||||
toast.success("Товар добавлен в поставку");
|
||||
};
|
||||
|
||||
const removeFromSupply = (nmID: number) => {
|
||||
setSupplyItems(prev => prev.filter(item => item.card.nmID !== nmID))
|
||||
}
|
||||
setSupplyItems((prev) => prev.filter((item) => item.card.nmID !== nmID));
|
||||
};
|
||||
|
||||
const updateSupplyItem = (nmID: number, field: keyof SupplyItem, value: string | number) => {
|
||||
setSupplyItems(prev => prev.map(item => {
|
||||
const updateSupplyItem = (
|
||||
nmID: number,
|
||||
field: keyof SupplyItem,
|
||||
value: string | number
|
||||
) => {
|
||||
setSupplyItems((prev) =>
|
||||
prev.map((item) => {
|
||||
if (item.card.nmID === nmID) {
|
||||
const updatedItem = { ...item, [field]: value }
|
||||
if (field === 'quantity' || field === 'pricePerUnit') {
|
||||
updatedItem.totalPrice = updatedItem.quantity * updatedItem.pricePerUnit
|
||||
const updatedItem = { ...item, [field]: value };
|
||||
if (field === "quantity" || field === "pricePerUnit") {
|
||||
updatedItem.totalPrice =
|
||||
updatedItem.quantity * updatedItem.pricePerUnit;
|
||||
}
|
||||
return updatedItem
|
||||
}
|
||||
return item
|
||||
}))
|
||||
return updatedItem;
|
||||
}
|
||||
return item;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
// Работа с поставщиками
|
||||
const handleCreateSupplier = () => {
|
||||
if (!newSupplier.name || !newSupplier.contactName || !newSupplier.phone) {
|
||||
toast.error('Заполните обязательные поля')
|
||||
return
|
||||
toast.error("Заполните обязательные поля");
|
||||
return;
|
||||
}
|
||||
|
||||
const supplier: Supplier = {
|
||||
id: Date.now().toString(),
|
||||
...newSupplier
|
||||
}
|
||||
...newSupplier,
|
||||
};
|
||||
|
||||
setSuppliers(prev => [...prev, supplier])
|
||||
setSuppliers((prev) => [...prev, supplier]);
|
||||
setNewSupplier({
|
||||
name: '',
|
||||
contactName: '',
|
||||
phone: '',
|
||||
market: '',
|
||||
address: '',
|
||||
place: '',
|
||||
telegram: ''
|
||||
})
|
||||
setShowSupplierModal(false)
|
||||
toast.success('Поставщик создан')
|
||||
}
|
||||
name: "",
|
||||
contactName: "",
|
||||
phone: "",
|
||||
market: "",
|
||||
address: "",
|
||||
place: "",
|
||||
telegram: "",
|
||||
});
|
||||
setShowSupplierModal(false);
|
||||
toast.success("Поставщик создан");
|
||||
};
|
||||
|
||||
// Расчеты
|
||||
// Расчеты для нового блока
|
||||
const getTotalSum = () => {
|
||||
return goodsPrice + fulfillmentServicesPrice + logisticsPrice;
|
||||
};
|
||||
|
||||
// Оригинальные расчеты
|
||||
const getTotalQuantity = () => {
|
||||
return supplyItems.reduce((sum, item) => sum + item.quantity, 0)
|
||||
}
|
||||
return supplyItems.reduce((sum, item) => sum + item.quantity, 0);
|
||||
};
|
||||
|
||||
const getTotalItemsCost = () => {
|
||||
return supplyItems.reduce((sum, item) => sum + item.totalPrice, 0)
|
||||
}
|
||||
return supplyItems.reduce((sum, item) => sum + item.totalPrice, 0);
|
||||
};
|
||||
|
||||
const getServicesCost = () => {
|
||||
if (!selectedFulfillmentOrg || selectedServices.length === 0) return 0
|
||||
if (!selectedFulfillmentOrg || selectedServices.length === 0) return 0;
|
||||
|
||||
const services = organizationServices[selectedFulfillmentOrg] || []
|
||||
return selectedServices.reduce((sum, serviceId) => {
|
||||
const service = services.find(s => s.id === serviceId)
|
||||
return sum + (service ? service.price : 0)
|
||||
const services = organizationServices[selectedFulfillmentOrg] || [];
|
||||
return (
|
||||
selectedServices.reduce((sum, serviceId) => {
|
||||
const service = services.find((s) => s.id === serviceId);
|
||||
return sum + (service ? service.price : 0);
|
||||
}, 0) * getTotalQuantity()
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getConsumablesCost = () => {
|
||||
if (!selectedFulfillmentOrg || selectedConsumables.length === 0) return 0
|
||||
if (!selectedFulfillmentOrg || selectedConsumables.length === 0) return 0;
|
||||
|
||||
const supplies = organizationSupplies[selectedFulfillmentOrg] || []
|
||||
return selectedConsumables.reduce((sum, supplyId) => {
|
||||
const supply = supplies.find(s => s.id === supplyId)
|
||||
return sum + (supply ? supply.price : 0)
|
||||
const supplies = organizationSupplies[selectedFulfillmentOrg] || [];
|
||||
return (
|
||||
selectedConsumables.reduce((sum, supplyId) => {
|
||||
const supply = supplies.find((s) => s.id === supplyId);
|
||||
return sum + (supply ? supply.price : 0);
|
||||
}, 0) * getTotalQuantity()
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const formatCurrency = (amount: number) => {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
style: 'currency',
|
||||
currency: 'RUB',
|
||||
minimumFractionDigits: 0
|
||||
}).format(amount)
|
||||
}
|
||||
return new Intl.NumberFormat("ru-RU", {
|
||||
style: "currency",
|
||||
currency: "RUB",
|
||||
minimumFractionDigits: 0,
|
||||
}).format(amount);
|
||||
};
|
||||
|
||||
// Создание поставки
|
||||
const handleCreateSupplyInternal = async () => {
|
||||
if (supplyItems.length === 0) {
|
||||
toast.error('Добавьте товары в поставку')
|
||||
return
|
||||
toast.error("Добавьте товары в поставку");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!deliveryDate) {
|
||||
toast.error('Выберите дату поставки')
|
||||
return
|
||||
if (!deliveryDateOriginal) {
|
||||
toast.error("Выберите дату поставки");
|
||||
return;
|
||||
}
|
||||
|
||||
if (supplyItems.some(item => item.quantity <= 0 || item.pricePerUnit <= 0)) {
|
||||
toast.error('Укажите количество и цену для всех товаров')
|
||||
return
|
||||
if (
|
||||
supplyItems.some((item) => item.quantity <= 0 || item.pricePerUnit <= 0)
|
||||
) {
|
||||
toast.error("Укажите количество и цену для всех товаров");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const supplyInput = {
|
||||
deliveryDate: deliveryDate.toISOString().split('T')[0],
|
||||
cards: supplyItems.map(item => ({
|
||||
deliveryDate: deliveryDateOriginal.toISOString().split("T")[0],
|
||||
cards: supplyItems.map((item) => ({
|
||||
nmId: item.card.nmID.toString(),
|
||||
vendorCode: item.card.vendorCode,
|
||||
title: item.card.title,
|
||||
@ -471,87 +624,84 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
selectedFulfillmentServices: selectedServices,
|
||||
selectedConsumableOrg: selectedFulfillmentOrg,
|
||||
selectedConsumableServices: selectedConsumables,
|
||||
deliveryDate: deliveryDate.toISOString().split('T')[0],
|
||||
mediaFiles: item.card.mediaFiles
|
||||
}))
|
||||
}
|
||||
deliveryDate: deliveryDateOriginal.toISOString().split("T")[0],
|
||||
mediaFiles: item.card.mediaFiles,
|
||||
})),
|
||||
};
|
||||
|
||||
await createSupply({ variables: { input: supplyInput } })
|
||||
toast.success('Поставка успешно создана!')
|
||||
onComplete()
|
||||
await createSupply({ variables: { input: supplyInput } });
|
||||
toast.success("Поставка успешно создана!");
|
||||
onComplete();
|
||||
} catch (error) {
|
||||
console.error('Error creating supply:', error)
|
||||
toast.error('Ошибка при создании поставки')
|
||||
}
|
||||
console.error("Error creating supply:", error);
|
||||
toast.error("Ошибка при создании поставки");
|
||||
}
|
||||
};
|
||||
|
||||
// Обработка внешнего вызова создания поставки
|
||||
React.useEffect(() => {
|
||||
if (isCreatingSupply) {
|
||||
handleCreateSupplyInternal()
|
||||
handleCreateSupplyInternal();
|
||||
}
|
||||
}, [isCreatingSupply])
|
||||
}, [isCreatingSupply]);
|
||||
|
||||
// Обновление статуса возможности создания поставки
|
||||
React.useEffect(() => {
|
||||
const canCreate = supplyItems.length > 0 &&
|
||||
deliveryDate !== null &&
|
||||
supplyItems.every(item => item.quantity > 0 && item.pricePerUnit > 0)
|
||||
const canCreate =
|
||||
supplyItems.length > 0 &&
|
||||
deliveryDateOriginal !== null &&
|
||||
supplyItems.every((item) => item.quantity > 0 && item.pricePerUnit > 0);
|
||||
|
||||
if (onCanCreateSupplyChange) {
|
||||
onCanCreateSupplyChange(canCreate)
|
||||
onCanCreateSupplyChange(canCreate);
|
||||
}
|
||||
}, [supplyItems, deliveryDate, onCanCreateSupplyChange])
|
||||
}, [supplyItems, deliveryDateOriginal, onCanCreateSupplyChange]);
|
||||
|
||||
const fulfillmentOrgs = (counterpartiesData?.myCounterparties || []).filter((org: Organization) => org.type === 'FULFILLMENT')
|
||||
const fulfillmentOrgs = (counterpartiesData?.myCounterparties || []).filter(
|
||||
(org: Organization) => org.type === "FULFILLMENT"
|
||||
);
|
||||
const markets = [
|
||||
{ value: 'sadovod', label: 'Садовод' },
|
||||
{ value: 'tyak-moscow', label: 'ТЯК Москва' }
|
||||
]
|
||||
{ value: "sadovod", label: "Садовод" },
|
||||
{ value: "tyak-moscow", label: "ТЯК Москва" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{/* Основные настройки */}
|
||||
<Card className="bg-gradient-to-r from-purple-500 to-pink-500 p-3">
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-6 gap-3 text-xs">
|
||||
{/* Дата поставки */}
|
||||
<>
|
||||
<style>{lineClampStyles}</style>
|
||||
<div className="space-y-3 w-full">
|
||||
{/* НОВЫЙ БЛОК СОЗДАНИЯ ПОСТАВКИ */}
|
||||
<Card className="bg-white/10 backdrop-blur-xl border border-white/20 p-3">
|
||||
{/* Первая строка */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 items-end mb-2">
|
||||
{/* 1. Модуль выбора даты */}
|
||||
<div>
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="w-full bg-white/20 rounded px-2 py-1 text-white hover:bg-white/30 transition-colors text-xs">
|
||||
<CalendarIcon className="h-3 w-3 inline mr-1" />
|
||||
{deliveryDate ? format(deliveryDate, "dd.MM") : "Дата"}
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0">
|
||||
<DatePicker
|
||||
selected={deliveryDate}
|
||||
onChange={(date: Date | null) => setDeliveryDate(date || undefined)}
|
||||
minDate={new Date()}
|
||||
inline
|
||||
locale="ru"
|
||||
<Label className="text-white/80 text-xs mb-1 block flex items-center gap-1">
|
||||
<CalendarIcon className="h-3 w-3" />
|
||||
Дата
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="date"
|
||||
value={deliveryDate}
|
||||
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"
|
||||
min={new Date().toISOString().split("T")[0]}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Фулфилмент */}
|
||||
<div className="md:col-span-2">
|
||||
{/* 2. Модуль выбора фулфилмента */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block flex items-center gap-1">
|
||||
<Building className="h-3 w-3" />
|
||||
Фулфилмент
|
||||
</Label>
|
||||
<Select
|
||||
value={selectedFulfillmentOrg}
|
||||
onValueChange={(value) => {
|
||||
setSelectedFulfillmentOrg(value)
|
||||
setSelectedServices([])
|
||||
setSelectedConsumables([])
|
||||
if (value) {
|
||||
loadOrganizationServices(value)
|
||||
loadOrganizationSupplies(value)
|
||||
}
|
||||
}}
|
||||
value={selectedFulfillment}
|
||||
onValueChange={setSelectedFulfillment}
|
||||
>
|
||||
<SelectTrigger className="bg-white/20 border-0 text-white h-7 text-xs">
|
||||
<SelectValue placeholder="Фулфилмент" />
|
||||
<SelectTrigger className="h-8 bg-white/20 border-0 text-white focus:bg-white/30 focus:ring-1 focus:ring-white/20 text-xs">
|
||||
<SelectValue placeholder="ФУЛФИЛМЕНТ ИВАНОВО" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fulfillmentOrgs.map((org: Organization) => (
|
||||
@ -563,64 +713,231 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Показатели */}
|
||||
<div className="text-center">
|
||||
<div className="text-white/80">Товаров</div>
|
||||
<div className="font-bold text-white">{getTotalQuantity()}</div>
|
||||
{/* 3. Объём товаров */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block">
|
||||
Объём товаров
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={goodsVolume || ""}
|
||||
onChange={(e) =>
|
||||
setGoodsVolume(parseFloat(e.target.value) || 0)
|
||||
}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-white/80">Стоимость</div>
|
||||
<div className="font-bold text-white">{formatCurrency(getTotalItemsCost()).replace(' ₽', '₽')}</div>
|
||||
|
||||
{/* 4. Грузовые места */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block">
|
||||
Грузовые места
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={cargoPlaces || ""}
|
||||
onChange={(e) => setCargoPlaces(parseInt(e.target.value) || 0)}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Вторая строка */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-4 gap-3 items-end">
|
||||
{/* 5. Цена товаров */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block">
|
||||
Цена товаров
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={goodsPrice || ""}
|
||||
onChange={(e) => setGoodsPrice(parseFloat(e.target.value) || 0)}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 6. Цена услуг фулфилмента */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block">
|
||||
Цена услуг фулфилмент
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={fulfillmentServicesPrice || ""}
|
||||
onChange={(e) =>
|
||||
setFulfillmentServicesPrice(parseFloat(e.target.value) || 0)
|
||||
}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 7. Цена логистики */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block">
|
||||
Логистика до фулфилмента
|
||||
</Label>
|
||||
<Input
|
||||
type="number"
|
||||
value={logisticsPrice || ""}
|
||||
onChange={(e) =>
|
||||
setLogisticsPrice(parseFloat(e.target.value) || 0)
|
||||
}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 8. Итоговая сумма */}
|
||||
<div>
|
||||
<Label className="text-white/80 text-xs mb-1 block">Итого</Label>
|
||||
<div className="h-8 bg-white/10 rounded-lg flex items-center justify-center">
|
||||
<span className="text-white font-bold text-sm">
|
||||
{formatCurrency(getTotalSum()).replace(" ₽", " ₽")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<div className="text-white/80">Услуги ФФ</div>
|
||||
<div className="font-bold text-white">{formatCurrency(getServicesCost() + getConsumablesCost()).replace(' ₽', '₽')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Поиск и карточки */}
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||
<div className="flex items-center space-x-2 mb-2">
|
||||
{/* Блок поиска товаров - оптимизированное расположение */}
|
||||
<Card className="bg-white/10 backdrop-blur-xl border border-white/20 p-3">
|
||||
<div className="mb-1">
|
||||
<Label className="text-white/80 text-xs mb-2 block flex items-center gap-1">
|
||||
<Search className="h-3 w-3" />
|
||||
Поиск товаров Wildberries
|
||||
</Label>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Input
|
||||
placeholder="Поиск товаров..."
|
||||
placeholder="Введите название товара, артикул или бренд..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="bg-white/5 border-white/20 text-white placeholder-white/50 h-7 text-xs flex-1"
|
||||
onKeyPress={(e) => e.key === 'Enter' && searchCards()}
|
||||
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"
|
||||
onKeyPress={(e) => e.key === "Enter" && searchCards()}
|
||||
/>
|
||||
<Button onClick={searchCards} disabled={loading} variant="secondary" size="sm" className="h-7 px-2 text-xs">
|
||||
<Search className="h-3 w-3" />
|
||||
<Button
|
||||
onClick={searchCards}
|
||||
disabled={loading}
|
||||
className="h-8 px-4 bg-white/20 hover:bg-white/30 border-0 text-white text-xs font-medium backdrop-blur"
|
||||
>
|
||||
{loading ? (
|
||||
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-white"></div>
|
||||
) : (
|
||||
<>
|
||||
<Search className="h-3 w-3 mr-1" />
|
||||
Поиск
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex space-x-2 overflow-x-auto pb-1">
|
||||
{loading ? (
|
||||
[...Array(6)].map((_, i) => (
|
||||
<div key={i} className="flex-shrink-0 w-16 h-20 bg-white/5 rounded animate-pulse"></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 && (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="bg-purple-500/20 text-purple-200 text-xs"
|
||||
>
|
||||
В поставке: {supplyItems.length}
|
||||
</Badge>
|
||||
)}
|
||||
</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
|
||||
? [...Array(12)].map((_, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="aspect-[3/4] bg-white/5 rounded-lg animate-pulse"
|
||||
></div>
|
||||
))
|
||||
) : (
|
||||
wbCards.map((card) => {
|
||||
const isInSupply = supplyItems.some(item => item.card.nmID === card.nmID)
|
||||
: wbCards.map((card) => {
|
||||
const isInSupply = supplyItems.some(
|
||||
(item) => item.card.nmID === card.nmID
|
||||
);
|
||||
return (
|
||||
<div
|
||||
key={card.nmID}
|
||||
className={`flex-shrink-0 w-16 cursor-pointer transition-all hover:scale-105 relative ${isInSupply ? 'ring-1 ring-purple-400' : ''}`}
|
||||
className={`group relative cursor-pointer transition-all duration-200 hover:scale-105 hover:z-10 ${
|
||||
isInSupply
|
||||
? "ring-2 ring-purple-400 ring-offset-1 ring-offset-transparent"
|
||||
: ""
|
||||
}`}
|
||||
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">
|
||||
<img
|
||||
src={WildberriesService.getCardImage(card, 'c246x328') || '/api/placeholder/64/80'}
|
||||
src={
|
||||
WildberriesService.getCardImage(
|
||||
card,
|
||||
"c516x688"
|
||||
) || "/api/placeholder/120/160"
|
||||
}
|
||||
alt={card.title}
|
||||
className="w-16 h-20 rounded object-cover"
|
||||
className="w-full h-full object-cover transition-transform group-hover:scale-110"
|
||||
loading="lazy"
|
||||
/>
|
||||
{isInSupply && (
|
||||
<div className="absolute -top-1 -right-1 bg-purple-500 text-white rounded-full w-3 h-3 flex items-center justify-center text-[8px]">
|
||||
✓
|
||||
|
||||
{/* Оверлей с информацией */}
|
||||
<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 bottom-2 left-2 right-2">
|
||||
<div className="text-white text-xs font-medium line-clamp-2 mb-1">
|
||||
{card.title}
|
||||
</div>
|
||||
<div className="text-white/70 text-[10px]">
|
||||
Арт: {card.vendorCode}
|
||||
</div>
|
||||
{card.sizes && card.sizes[0] && (
|
||||
<div className="text-purple-300 text-[10px] font-medium">
|
||||
от{" "}
|
||||
{card.sizes[0].discountedPrice ||
|
||||
card.sizes[0].price}{" "}
|
||||
₽
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
</div>
|
||||
|
||||
{/* Индикатор добавления в поставку */}
|
||||
{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>
|
||||
)}
|
||||
|
||||
{/* Индикатор при наведении */}
|
||||
<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">
|
||||
<Plus className="h-3 w-3" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{!loading && wbCards.length === 0 && (
|
||||
<div className="text-center py-4">
|
||||
<Package className="h-8 w-8 text-white/20 mx-auto mb-2" />
|
||||
<p className="text-white/60 text-xs">
|
||||
{searchTerm
|
||||
? "Товары не найдены"
|
||||
: "Введите запрос для поиска товаров"}
|
||||
</p>
|
||||
{searchTerm && (
|
||||
<p className="text-white/40 text-[10px] mt-1">
|
||||
Попробуйте изменить условия поиска
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
@ -633,23 +950,35 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<div className="text-white/80 mb-1">Услуги фулфилмента:</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{organizationServices[selectedFulfillmentOrg] ? (
|
||||
organizationServices[selectedFulfillmentOrg].map((service) => (
|
||||
<label key={service.id} className="flex items-center space-x-1 cursor-pointer bg-white/5 rounded px-2 py-1 hover:bg-white/10">
|
||||
organizationServices[selectedFulfillmentOrg].map(
|
||||
(service) => (
|
||||
<label
|
||||
key={service.id}
|
||||
className="flex items-center space-x-1 cursor-pointer bg-white/5 rounded px-2 py-1 hover:bg-white/10"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedServices.includes(service.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedServices(prev => [...prev, service.id])
|
||||
setSelectedServices((prev) => [
|
||||
...prev,
|
||||
service.id,
|
||||
]);
|
||||
} else {
|
||||
setSelectedServices(prev => prev.filter(id => id !== service.id))
|
||||
setSelectedServices((prev) =>
|
||||
prev.filter((id) => id !== service.id)
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<span className="text-white text-xs">{service.name} ({service.price}₽)</span>
|
||||
<span className="text-white text-xs">
|
||||
{service.name} ({service.price}₽)
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<span className="text-white/60">Загрузка...</span>
|
||||
)}
|
||||
@ -660,23 +989,35 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<div className="text-white/80 mb-1">Расходные материалы:</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{organizationSupplies[selectedFulfillmentOrg] ? (
|
||||
organizationSupplies[selectedFulfillmentOrg].map((supply) => (
|
||||
<label key={supply.id} className="flex items-center space-x-1 cursor-pointer bg-white/5 rounded px-2 py-1 hover:bg-white/10">
|
||||
organizationSupplies[selectedFulfillmentOrg].map(
|
||||
(supply) => (
|
||||
<label
|
||||
key={supply.id}
|
||||
className="flex items-center space-x-1 cursor-pointer bg-white/5 rounded px-2 py-1 hover:bg-white/10"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedConsumables.includes(supply.id)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedConsumables(prev => [...prev, supply.id])
|
||||
setSelectedConsumables((prev) => [
|
||||
...prev,
|
||||
supply.id,
|
||||
]);
|
||||
} else {
|
||||
setSelectedConsumables(prev => prev.filter(id => id !== supply.id))
|
||||
setSelectedConsumables((prev) =>
|
||||
prev.filter((id) => id !== supply.id)
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="w-3 h-3"
|
||||
/>
|
||||
<span className="text-white text-xs">{supply.name} ({supply.price}₽)</span>
|
||||
<span className="text-white text-xs">
|
||||
{supply.name} ({supply.price}₽)
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<span className="text-white/60">Загрузка...</span>
|
||||
)}
|
||||
@ -686,15 +1027,17 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{/* Компактная таблица товаров */}
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-2">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-white font-medium text-sm">Товары в поставке</span>
|
||||
{/* Модуль товаров в поставке - новый дизайн */}
|
||||
<Card className="bg-white/10 backdrop-blur border-white/20 p-3">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-white font-medium text-sm">
|
||||
Товары в поставке
|
||||
</span>
|
||||
<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"
|
||||
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" />
|
||||
Поставщик
|
||||
@ -702,57 +1045,200 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
</div>
|
||||
|
||||
{supplyItems.length === 0 ? (
|
||||
<div className="text-center py-4">
|
||||
<Package className="h-6 w-6 text-white/20 mx-auto mb-1" />
|
||||
<p className="text-white/60 text-xs">Добавьте товары из карточек выше</p>
|
||||
<div className="text-center py-6">
|
||||
<Package className="h-8 w-8 text-white/20 mx-auto mb-2" />
|
||||
<p className="text-white/60 text-xs">
|
||||
Добавьте товары из карточек выше
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-1">
|
||||
<div className="space-y-4">
|
||||
{supplyItems.map((item) => (
|
||||
<div key={item.card.nmID} className="grid grid-cols-12 gap-2 items-center bg-white/5 rounded p-2 text-xs">
|
||||
{/* Товар */}
|
||||
<div className="col-span-4 flex items-center space-x-2">
|
||||
<img
|
||||
src={WildberriesService.getCardImage(item.card, 'c246x328') || '/api/placeholder/24/24'}
|
||||
alt={item.card.title}
|
||||
className="w-6 h-6 rounded object-cover"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<div className="text-white font-medium truncate text-xs">{item.card.title}</div>
|
||||
<div className="text-white/60 text-[10px]">Арт: {item.card.vendorCode}</div>
|
||||
<Card
|
||||
key={item.card.nmID}
|
||||
className="bg-white/5 border-white/10 p-3"
|
||||
>
|
||||
{/* Заголовок товара с кнопкой удаления */}
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center space-x-2">
|
||||
<div className="text-white font-medium text-sm line-clamp-1">
|
||||
{item.card.title}
|
||||
</div>
|
||||
<div className="text-white/60 text-xs">
|
||||
Арт: {item.card.vendorCode}
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => removeFromSupply(item.card.nmID)}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-6 w-6 p-0 text-white/60 hover:text-red-400"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Названия блоков */}
|
||||
<div
|
||||
className="grid grid-cols-8 gap-2"
|
||||
style={{ marginBottom: "4px" }}
|
||||
>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Товар
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Параметры
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Заказать
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Цена
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Услуги фулфилмента
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Поставщик
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Расходники фулфилмента
|
||||
</div>
|
||||
<div className="text-white/80 text-xs font-medium text-center">
|
||||
Расходники селлера
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Количество */}
|
||||
<div className="col-span-2">
|
||||
{/* Оптимизированная сетка для 13" - все блоки в одну строку */}
|
||||
<div className="grid grid-cols-8 gap-2">
|
||||
{/* Блок 1: Картинка товара */}
|
||||
<div className="bg-white/10 rounded-lg overflow-hidden relative">
|
||||
<img
|
||||
src={
|
||||
WildberriesService.getCardImage(
|
||||
item.card,
|
||||
"c246x328"
|
||||
) || "/api/placeholder/60/60"
|
||||
}
|
||||
alt={item.card.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Блок 2: Параметры */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<div className="space-y-1">
|
||||
<div className="text-white/70 text-[9px] text-center">
|
||||
{item.card.object}
|
||||
</div>
|
||||
<div className="text-white/70 text-[9px] text-center">
|
||||
{item.card.countryProduction}
|
||||
</div>
|
||||
{item.card.sizes && item.card.sizes[0] && (
|
||||
<div className="text-white/70 text-[9px] text-center">
|
||||
{item.card.sizes[0].techSize}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Блок 3: Заказать */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<div className="text-white/60 text-[10px] mb-1 text-center">
|
||||
Количество
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
value={item.quantity}
|
||||
onChange={(e) => updateSupplyItem(item.card.nmID, 'quantity', parseInt(e.target.value) || 0)}
|
||||
className="bg-purple-500 border-0 text-white text-center h-6 text-xs font-bold"
|
||||
onChange={(e) =>
|
||||
updateSupplyItem(
|
||||
item.card.nmID,
|
||||
"quantity",
|
||||
parseInt(e.target.value) || 0
|
||||
)
|
||||
}
|
||||
className="bg-purple-500/20 border-purple-400/30 text-white text-center h-7 text-xs font-bold"
|
||||
min="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Цена */}
|
||||
<div className="col-span-2">
|
||||
{/* Блок 4: Цена */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<div className="text-white/60 text-[10px] mb-1 text-center">
|
||||
За единицу
|
||||
</div>
|
||||
<Input
|
||||
type="number"
|
||||
value={item.pricePerUnit || ''}
|
||||
onChange={(e) => updateSupplyItem(item.card.nmID, 'pricePerUnit', parseFloat(e.target.value) || 0)}
|
||||
className="bg-white/10 border-white/20 text-white text-center h-6 text-xs"
|
||||
placeholder="Цена"
|
||||
value={item.pricePerUnit || ""}
|
||||
onChange={(e) =>
|
||||
updateSupplyItem(
|
||||
item.card.nmID,
|
||||
"pricePerUnit",
|
||||
parseFloat(e.target.value) || 0
|
||||
)
|
||||
}
|
||||
className="bg-white/20 border-white/20 text-white text-center h-7 text-xs"
|
||||
placeholder="₽"
|
||||
/>
|
||||
<div className="text-white/80 text-[9px] font-medium text-center mt-1">
|
||||
{formatCurrency(item.totalPrice).replace(" ₽", "₽")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Поставщик */}
|
||||
<div className="col-span-3">
|
||||
{/* Блок 5: Услуги фулфилмента */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<div className="space-y-1 max-h-16 overflow-y-auto">
|
||||
{selectedFulfillmentOrg &&
|
||||
organizationServices[selectedFulfillmentOrg] ? (
|
||||
organizationServices[selectedFulfillmentOrg]
|
||||
.slice(0, 2)
|
||||
.map((service) => (
|
||||
<label
|
||||
key={service.id}
|
||||
className="flex items-center space-x-1 cursor-pointer"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedServices.includes(
|
||||
service.id
|
||||
)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedServices((prev) => [
|
||||
...prev,
|
||||
service.id,
|
||||
]);
|
||||
} else {
|
||||
setSelectedServices((prev) =>
|
||||
prev.filter((id) => id !== service.id)
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="w-2 h-2"
|
||||
/>
|
||||
<span className="text-white text-[9px]">
|
||||
{service.name.substring(0, 8)}...
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
) : (
|
||||
<span className="text-white/60 text-[9px] text-center">
|
||||
Выберите фулфилмент
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Блок 6: Поставщик */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<Select
|
||||
value={item.supplierId}
|
||||
onValueChange={(value) => updateSupplyItem(item.card.nmID, 'supplierId', value)}
|
||||
onValueChange={(value) =>
|
||||
updateSupplyItem(item.card.nmID, "supplierId", value)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-white/5 border-white/20 text-white h-6 text-xs">
|
||||
<SelectValue placeholder="Поставщик" />
|
||||
<SelectTrigger className="bg-white/20 border-white/20 text-white h-7 text-[10px]">
|
||||
<SelectValue placeholder="Выбрать" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{suppliers.map((supplier) => (
|
||||
@ -764,21 +1250,73 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Сумма и удаление */}
|
||||
<div className="col-span-1 flex items-center justify-between">
|
||||
<span className="text-white font-bold text-xs">
|
||||
{formatCurrency(item.totalPrice).replace(' ₽', '₽')}
|
||||
</span>
|
||||
<Button
|
||||
onClick={() => removeFromSupply(item.card.nmID)}
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-4 w-4 p-0 text-white/60 hover:text-red-400"
|
||||
{/* Блок 7: Расходники фулфилмента */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<div className="space-y-1 max-h-16 overflow-y-auto">
|
||||
{selectedFulfillmentOrg &&
|
||||
organizationSupplies[selectedFulfillmentOrg] ? (
|
||||
organizationSupplies[selectedFulfillmentOrg]
|
||||
.slice(0, 2)
|
||||
.map((supply) => (
|
||||
<label
|
||||
key={supply.id}
|
||||
className="flex items-center space-x-1 cursor-pointer"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</Button>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedConsumables.includes(
|
||||
supply.id
|
||||
)}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedConsumables((prev) => [
|
||||
...prev,
|
||||
supply.id,
|
||||
]);
|
||||
} else {
|
||||
setSelectedConsumables((prev) =>
|
||||
prev.filter((id) => id !== supply.id)
|
||||
);
|
||||
}
|
||||
}}
|
||||
className="w-2 h-2"
|
||||
/>
|
||||
<span className="text-white text-[9px]">
|
||||
{supply.name.substring(0, 6)}...
|
||||
</span>
|
||||
</label>
|
||||
))
|
||||
) : (
|
||||
<span className="text-white/60 text-[9px] text-center">
|
||||
Выберите фулфилмент
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Блок 8: Расходники селлера */}
|
||||
<div className="bg-white/10 rounded-lg p-2 flex flex-col justify-center">
|
||||
<div className="space-y-1">
|
||||
<label className="flex items-center space-x-1 cursor-pointer">
|
||||
<input type="checkbox" className="w-2 h-2" />
|
||||
<span className="text-white text-[9px]">
|
||||
Упаковка
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center space-x-1 cursor-pointer">
|
||||
<input type="checkbox" className="w-2 h-2" />
|
||||
<span className="text-white text-[9px]">
|
||||
Этикетки
|
||||
</span>
|
||||
</label>
|
||||
<label className="flex items-center space-x-1 cursor-pointer">
|
||||
<input type="checkbox" className="w-2 h-2" />
|
||||
<span className="text-white text-[9px]">Пакеты</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@ -788,7 +1326,9 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Dialog open={showSupplierModal} onOpenChange={setShowSupplierModal}>
|
||||
<DialogContent className="glass-card border-white/10 max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="text-white">Создать поставщика</DialogTitle>
|
||||
<DialogTitle className="text-white">
|
||||
Создать поставщика
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
@ -796,7 +1336,12 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Название *</Label>
|
||||
<Input
|
||||
value={newSupplier.name}
|
||||
onChange={(e) => setNewSupplier(prev => ({ ...prev, name: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSupplier((prev) => ({
|
||||
...prev,
|
||||
name: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||
placeholder="Название"
|
||||
/>
|
||||
@ -805,7 +1350,12 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Имя *</Label>
|
||||
<Input
|
||||
value={newSupplier.contactName}
|
||||
onChange={(e) => setNewSupplier(prev => ({ ...prev, contactName: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSupplier((prev) => ({
|
||||
...prev,
|
||||
contactName: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||
placeholder="Имя"
|
||||
/>
|
||||
@ -817,7 +1367,12 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Телефон *</Label>
|
||||
<Input
|
||||
value={newSupplier.phone}
|
||||
onChange={(e) => setNewSupplier(prev => ({ ...prev, phone: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSupplier((prev) => ({
|
||||
...prev,
|
||||
phone: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||
placeholder="+7 999 123-45-67"
|
||||
/>
|
||||
@ -826,7 +1381,9 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Рынок</Label>
|
||||
<Select
|
||||
value={newSupplier.market}
|
||||
onValueChange={(value) => setNewSupplier(prev => ({ ...prev, market: value }))}
|
||||
onValueChange={(value) =>
|
||||
setNewSupplier((prev) => ({ ...prev, market: value }))
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="bg-white/10 border-white/20 text-white h-8 text-xs">
|
||||
<SelectValue placeholder="Рынок" />
|
||||
@ -847,7 +1404,12 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Адрес</Label>
|
||||
<Input
|
||||
value={newSupplier.address}
|
||||
onChange={(e) => setNewSupplier(prev => ({ ...prev, address: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSupplier((prev) => ({
|
||||
...prev,
|
||||
address: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||
placeholder="Адрес"
|
||||
/>
|
||||
@ -856,7 +1418,12 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Место</Label>
|
||||
<Input
|
||||
value={newSupplier.place}
|
||||
onChange={(e) => setNewSupplier(prev => ({ ...prev, place: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSupplier((prev) => ({
|
||||
...prev,
|
||||
place: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||
placeholder="Павильон/место"
|
||||
/>
|
||||
@ -867,7 +1434,12 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
<Label className="text-white/60 text-xs">Телеграм</Label>
|
||||
<Input
|
||||
value={newSupplier.telegram}
|
||||
onChange={(e) => setNewSupplier(prev => ({ ...prev, telegram: e.target.value }))}
|
||||
onChange={(e) =>
|
||||
setNewSupplier((prev) => ({
|
||||
...prev,
|
||||
telegram: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="bg-white/10 border-white/20 text-white h-8 text-xs"
|
||||
placeholder="@username"
|
||||
/>
|
||||
@ -892,5 +1464,6 @@ export function DirectSupplyCreation({ onComplete, onCreateSupply, canCreateSupp
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
)
|
||||
</>
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user