Refactor: Replace wholesaler with supplier terminology and add fulfillment consumables logic

This commit is contained in:
Veronika Smirnova
2025-07-30 17:03:31 +03:00
parent e351752b09
commit 3e7ea13026
31 changed files with 3343 additions and 1538 deletions

View File

@ -0,0 +1,468 @@
"use client";
import React, { useState } from "react";
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 {
ArrowLeft,
Package,
Plus,
Minus,
ShoppingCart,
Eye,
Info,
} from "lucide-react";
import Image from "next/image";
interface Supplier {
id: string;
inn: string;
name: string;
fullName: string;
address: string;
phone?: string;
email?: string;
rating: number;
productCount: number;
avatar?: string;
specialization: string[];
}
interface Product {
id: string;
name: string;
article: string;
description: string;
price: number;
quantity: number;
category: string;
brand?: string;
color?: string;
size?: string;
weight?: number;
dimensions?: string;
material?: string;
images: string[];
mainImage?: string;
}
interface SelectedProduct extends Product {
selectedQuantity: number;
}
interface SupplierProductsProps {
supplier: Supplier;
onBack: () => void;
onClose: () => void;
onSupplyCreated: () => void;
}
// Моковые данные товаров
const mockProducts: Product[] = [
{
id: "1",
name: "Смартфон Samsung Galaxy A54",
article: "SGX-A54-128",
description: 'Смартфон с экраном 6.4", камерой 50 МП, 128 ГБ памяти',
price: 28900,
quantity: 150,
category: "Смартфоны",
brand: "Samsung",
color: "Черный",
size: '6.4"',
weight: 202,
dimensions: "158.2 x 76.7 x 8.2 мм",
material: "Алюминий, стекло",
images: ["/api/placeholder/300/300?text=Samsung+A54"],
mainImage: "/api/placeholder/300/300?text=Samsung+A54",
},
{
id: "2",
name: "Наушники Sony WH-1000XM4",
article: "SNY-WH1000XM4",
description: "Беспроводные наушники с шумоподавлением",
price: 24900,
quantity: 85,
category: "Наушники",
brand: "Sony",
color: "Черный",
weight: 254,
material: "Пластик, кожа",
images: ["/api/placeholder/300/300?text=Sony+WH1000XM4"],
mainImage: "/api/placeholder/300/300?text=Sony+WH1000XM4",
},
{
id: "3",
name: 'Планшет iPad Air 10.9"',
article: "APL-IPADAIR-64",
description: "Планшет Apple iPad Air с чипом M1, 64 ГБ",
price: 54900,
quantity: 45,
category: "Планшеты",
brand: "Apple",
color: "Серый космос",
size: '10.9"',
weight: 461,
dimensions: "247.6 x 178.5 x 6.1 мм",
material: "Алюминий",
images: ["/api/placeholder/300/300?text=iPad+Air"],
mainImage: "/api/placeholder/300/300?text=iPad+Air",
},
{
id: "4",
name: "Ноутбук Lenovo ThinkPad E15",
article: "LNV-TE15-I5",
description: 'Ноутбук 15.6" Intel Core i5, 8 ГБ ОЗУ, 256 ГБ SSD',
price: 45900,
quantity: 25,
category: "Ноутбуки",
brand: "Lenovo",
color: "Черный",
size: '15.6"',
weight: 1700,
dimensions: "365 x 240 x 19.9 мм",
material: "Пластик",
images: ["/api/placeholder/300/300?text=ThinkPad+E15"],
mainImage: "/api/placeholder/300/300?text=ThinkPad+E15",
},
{
id: "5",
name: "Умные часы Apple Watch SE",
article: "APL-AWSE-40",
description: "Умные часы Apple Watch SE 40 мм",
price: 21900,
quantity: 120,
category: "Умные часы",
brand: "Apple",
color: "Белый",
size: "40 мм",
weight: 30,
dimensions: "40 x 34 x 10.7 мм",
material: "Алюминий",
images: ["/api/placeholder/300/300?text=Apple+Watch+SE"],
mainImage: "/api/placeholder/300/300?text=Apple+Watch+SE",
},
{
id: "6",
name: "Клавиатура Logitech MX Keys",
article: "LGT-MXKEYS",
description: "Беспроводная клавиатура для продуктивной работы",
price: 8900,
quantity: 75,
category: "Клавиатуры",
brand: "Logitech",
color: "Графит",
weight: 810,
dimensions: "430.2 x 20.5 x 131.6 мм",
material: "Пластик, металл",
images: ["/api/placeholder/300/300?text=MX+Keys"],
mainImage: "/api/placeholder/300/300?text=MX+Keys",
},
];
export function SupplierProducts({
supplier,
onBack,
onClose,
onSupplyCreated,
}: SupplierProductsProps) {
const [selectedProducts, setSelectedProducts] = useState<SelectedProduct[]>(
[]
);
const [showSummary, setShowSummary] = useState(false);
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB",
minimumFractionDigits: 0,
}).format(amount);
};
const updateProductQuantity = (productId: string, quantity: number) => {
const product = mockProducts.find((p) => p.id === productId);
if (!product) return;
setSelectedProducts((prev) => {
const existing = prev.find((p) => p.id === productId);
if (quantity === 0) {
// Удаляем продукт если количество 0
return prev.filter((p) => p.id !== productId);
}
if (existing) {
// Обновляем количество существующего продукта
return prev.map((p) =>
p.id === productId ? { ...p, selectedQuantity: quantity } : p
);
} else {
// Добавляем новый продукт
return [...prev, { ...product, selectedQuantity: quantity }];
}
});
};
const getSelectedQuantity = (productId: string): number => {
const selected = selectedProducts.find((p) => p.id === productId);
return selected ? selected.selectedQuantity : 0;
};
const getTotalAmount = () => {
return selectedProducts.reduce(
(sum, product) => sum + product.price * product.selectedQuantity,
0
);
};
const getTotalItems = () => {
return selectedProducts.reduce(
(sum, product) => sum + product.selectedQuantity,
0
);
};
const handleCreateSupply = () => {
console.log("Создание поставки с товарами:", selectedProducts);
// TODO: Здесь будет реальное создание поставки
onSupplyCreated();
};
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-3">
<Button
variant="ghost"
size="sm"
onClick={onBack}
className="text-white/60 hover:text-white hover:bg-white/10"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Назад
</Button>
<div>
<h2 className="text-2xl font-bold text-white mb-1">
Товары поставщика
</h2>
<p className="text-white/60">
{supplier.name} {mockProducts.length} товаров
</p>
</div>
</div>
<div className="flex items-center space-x-3">
<Button
variant="ghost"
size="sm"
onClick={() => setShowSummary(!showSummary)}
className="text-white/60 hover:text-white hover:bg-white/10"
>
<Info className="h-4 w-4 mr-2" />
Резюме ({selectedProducts.length})
</Button>
<Button
variant="ghost"
size="sm"
onClick={onClose}
className="text-white/60 hover:text-white hover:bg-white/10"
>
Отмена
</Button>
</div>
</div>
{showSummary && selectedProducts.length > 0 && (
<Card className="bg-purple-500/10 backdrop-blur border-purple-500/30 p-6">
<h3 className="text-white font-semibold text-lg mb-4">
Резюме заказа
</h3>
<div className="space-y-3">
{selectedProducts.map((product) => (
<div
key={product.id}
className="flex justify-between items-center"
>
<div>
<span className="text-white">{product.name}</span>
<span className="text-white/60 text-sm ml-2">
× {product.selectedQuantity}
</span>
</div>
<span className="text-white font-medium">
{formatCurrency(product.price * product.selectedQuantity)}
</span>
</div>
))}
<div className="border-t border-white/20 pt-3 flex justify-between items-center">
<span className="text-white font-semibold">
Итого: {getTotalItems()} товаров
</span>
<span className="text-white font-bold text-xl">
{formatCurrency(getTotalAmount())}
</span>
</div>
<Button
className="w-full bg-gradient-to-r from-green-500 to-emerald-500 hover:from-green-600 hover:to-emerald-600 text-white"
onClick={handleCreateSupply}
disabled={selectedProducts.length === 0}
>
<ShoppingCart className="h-4 w-4 mr-2" />
Создать поставку
</Button>
</div>
</Card>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{mockProducts.map((product) => {
const selectedQuantity = getSelectedQuantity(product.id);
return (
<Card
key={product.id}
className="bg-white/10 backdrop-blur border-white/20 overflow-hidden"
>
<div className="aspect-square relative bg-white/5">
<Image
src={product.mainImage || "/api/placeholder/300/300"}
alt={product.name}
fill
className="object-cover"
/>
<div className="absolute top-2 right-2">
<Badge className="bg-green-500/20 text-green-300 border-green-500/30">
В наличии: {product.quantity}
</Badge>
</div>
</div>
<div className="p-4 space-y-3">
<div>
<h3 className="text-white font-semibold mb-1 line-clamp-2">
{product.name}
</h3>
<p className="text-white/60 text-xs mb-2">
Артикул: {product.article}
</p>
<div className="flex items-center space-x-2 mb-2">
<Badge className="bg-blue-500/20 text-blue-300 border-blue-500/30 text-xs">
{product.category}
</Badge>
{product.brand && (
<Badge className="bg-gray-500/20 text-gray-300 border-gray-500/30 text-xs">
{product.brand}
</Badge>
)}
</div>
</div>
<p className="text-white/60 text-sm line-clamp-2">
{product.description}
</p>
<div className="space-y-2">
{product.color && (
<div className="text-white/60 text-xs">
Цвет: <span className="text-white">{product.color}</span>
</div>
)}
{product.size && (
<div className="text-white/60 text-xs">
Размер: <span className="text-white">{product.size}</span>
</div>
)}
{product.weight && (
<div className="text-white/60 text-xs">
Вес:{" "}
<span className="text-white">{product.weight} г</span>
</div>
)}
</div>
<div className="flex items-center justify-between pt-2 border-t border-white/10">
<div>
<div className="text-white font-bold text-lg">
{formatCurrency(product.price)}
</div>
<div className="text-white/60 text-xs">за штуку</div>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
variant="ghost"
size="sm"
onClick={() =>
updateProductQuantity(
product.id,
Math.max(0, selectedQuantity - 1)
)
}
disabled={selectedQuantity === 0}
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10"
>
<Minus className="h-4 w-4" />
</Button>
<Input
type="number"
value={selectedQuantity}
onChange={(e) => {
const value = Math.max(
0,
Math.min(
product.quantity,
parseInt(e.target.value) || 0
)
);
updateProductQuantity(product.id, value);
}}
className="h-8 w-16 text-center bg-white/10 border-white/20 text-white"
min={0}
max={product.quantity}
/>
<Button
variant="ghost"
size="sm"
onClick={() =>
updateProductQuantity(
product.id,
Math.min(product.quantity, selectedQuantity + 1)
)
}
disabled={selectedQuantity >= product.quantity}
className="h-8 w-8 p-0 text-white/60 hover:text-white hover:bg-white/10"
>
<Plus className="h-4 w-4" />
</Button>
</div>
{selectedQuantity > 0 && (
<div className="bg-green-500/20 border border-green-500/30 rounded-lg p-2">
<div className="text-green-300 text-sm font-medium">
Сумма: {formatCurrency(product.price * selectedQuantity)}
</div>
</div>
)}
</div>
</Card>
);
})}
</div>
{selectedProducts.length > 0 && (
<div className="fixed bottom-6 right-6">
<Button
className="bg-gradient-to-r from-purple-500 to-pink-500 hover:from-purple-600 hover:to-pink-600 text-white shadow-lg"
onClick={() => setShowSummary(!showSummary)}
>
<ShoppingCart className="h-4 w-4 mr-2" />
Корзина ({selectedProducts.length}) {" "}
{formatCurrency(getTotalAmount())}
</Button>
</div>
)}
</div>
);
}