From 0a3a2dae7bed8c7210bd856ca6d9a06d9ba1e93a Mon Sep 17 00:00:00 2001 From: Bivekich Date: Fri, 1 Aug 2025 11:05:44 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B0=20=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BC?= =?UTF-8?q?=D0=B0=20=D1=83=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=81=D0=BA=D0=BB=D0=B0=D0=B4=D0=BE=D0=BC=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B0=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B3=D0=BB=D0=B0=D1=81=D0=BD=D0=BE=20=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D0=B1=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=D0=BC=20rules1.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлена автогенерация артикулов СФ (SF-T для товаров, SF-C для расходников) - Добавлены поля для комплектов: количество и цена за комплект - Добавлены поля учета движения товаров: заказано, в пути, остаток, продано - Создан компонент статистики склада с общими показателями и разбивкой по типам - Реализовано переключение между карточным и табличным режимом отображения (5 карточек в ряду) - Обновлены GraphQL схемы для поддержки новых полей - Улучшен пользовательский интерфейс: убран заголовок, оптимизирована компоновка - Синхронизированы поля между карточками и таблицей для единообразного отображения 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/components/warehouse/product-card.tsx | 62 ++++- src/components/warehouse/product-form.tsx | 122 ++++++++- .../warehouse/warehouse-dashboard.tsx | 251 ++++++++++++------ .../warehouse/warehouse-statistics.tsx | 191 +++++++++++++ src/graphql/mutations.ts | 12 + src/graphql/queries.ts | 6 + src/graphql/typedefs.ts | 12 + 7 files changed, 560 insertions(+), 96 deletions(-) create mode 100644 src/components/warehouse/warehouse-statistics.tsx diff --git a/src/components/warehouse/product-card.tsx b/src/components/warehouse/product-card.tsx index 65d21c1..e5d99e9 100644 --- a/src/components/warehouse/product-card.tsx +++ b/src/components/warehouse/product-card.tsx @@ -18,7 +18,13 @@ interface Product { article: string description: string price: number + pricePerSet?: number quantity: number + setQuantity?: number + ordered?: number + inTransit?: number + stock?: number + sold?: number type: 'PRODUCT' | 'CONSUMABLE' category: { id: string; name: string } | null brand: string @@ -66,19 +72,25 @@ export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) { } const getStatusColor = () => { + const stock = product.stock || product.quantity || 0; if (!product.isActive) return 'bg-gray-500/20 text-gray-300 border-gray-400/30' - if (product.quantity === 0) return 'bg-red-500/20 text-red-300 border-red-400/30' - if (product.quantity < 10) return 'bg-yellow-500/20 text-yellow-300 border-yellow-400/30' + if (stock === 0) return 'bg-red-500/20 text-red-300 border-red-400/30' + if (stock < 10) return 'bg-yellow-500/20 text-yellow-300 border-yellow-400/30' return 'bg-green-500/20 text-green-300 border-green-400/30' } const getStatusText = () => { + const stock = product.stock || product.quantity || 0; if (!product.isActive) return 'Неактивен' - if (product.quantity === 0) return 'Нет в наличии' - if (product.quantity < 10) return 'Мало на складе' + if (stock === 0) return 'Нет в наличии' + if (stock < 10) return 'Мало на складе' return 'В наличии' } + const getStockQuantity = () => { + return product.stock || product.quantity || 0; + } + return ( {/* Изображение товара */} @@ -192,12 +204,44 @@ export function ProductCard({ product, onEdit, onDeleted }: ProductCardProps) { {/* Цена и количество */} -
-
- {formatPrice(product.price)} +
+
+
+
+ {formatPrice(product.price)} +
+ {product.pricePerSet && ( +
+ Комплект: {formatPrice(product.pricePerSet)} +
+ )} +
+
+
+ {getStockQuantity()} шт. +
+ {product.setQuantity && ( +
+ {product.setQuantity} компл. +
+ )} +
-
- {product.quantity} шт. + + {/* Учет движения */} +
+
+
{product.ordered || 0}
+
Заказано
+
+
+
{product.inTransit || 0}
+
В пути
+
+
+
{product.sold || 0}
+
Продано
+
diff --git a/src/components/warehouse/product-form.tsx b/src/components/warehouse/product-form.tsx index 02a69d5..dc3c395 100644 --- a/src/components/warehouse/product-form.tsx +++ b/src/components/warehouse/product-form.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useRef } from "react"; +import { useState, useRef, useEffect } from "react"; import Image from "next/image"; import { useMutation, useQuery } from "@apollo/client"; import { Button } from "@/components/ui/button"; @@ -16,7 +16,7 @@ import { import { Card } from "@/components/ui/card"; import { CREATE_PRODUCT, UPDATE_PRODUCT } from "@/graphql/mutations"; import { GET_CATEGORIES } from "@/graphql/queries"; -import { X, Star, Upload } from "lucide-react"; +import { X, Star, Upload, RefreshCw } from "lucide-react"; import { toast } from "sonner"; interface Product { @@ -49,9 +49,16 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { const [formData, setFormData] = useState({ name: product?.name || "", article: product?.article || "", + autoGenerateArticle: !product?.article, // Автогенерация только для новых товаров description: product?.description || "", price: product?.price || 0, quantity: product?.quantity || 0, + setQuantity: product?.setQuantity || 0, // Количество комплектов + pricePerSet: product?.pricePerSet || 0, // Цена за комплект + ordered: product?.ordered || 0, // Заказано + inTransit: product?.inTransit || 0, // В пути + stock: product?.stock || 0, // Остаток + sold: product?.sold || 0, // Продано type: product?.type || ("PRODUCT" as "PRODUCT" | "CONSUMABLE"), categoryId: product?.category?.id || "none", brand: product?.brand || "", @@ -79,6 +86,24 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { const loading = creating || updating; + // Генерация артикула СФ + const generateArticle = () => { + const prefix = formData.type === 'PRODUCT' ? 'SF-T' : 'SF-C'; // T=Товар, C=Расходник + const timestamp = Date.now().toString().slice(-6); // Последние 6 цифр timestamp + const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0'); + return `${prefix}-${timestamp}-${random}`; + }; + + // Автогенерация артикула при смене типа + useEffect(() => { + if (formData.autoGenerateArticle && formData.type) { + setFormData(prev => ({ + ...prev, + article: generateArticle() + })); + } + }, [formData.type, formData.autoGenerateArticle]); + const handleInputChange = ( field: string, value: string | number | boolean @@ -89,6 +114,13 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { })); }; + const handleGenerateNewArticle = () => { + setFormData(prev => ({ + ...prev, + article: generateArticle() + })); + }; + const handleImageUpload = async (files: FileList) => { const newUploadingIndexes = new Set(); const startIndex = formData.images.length; @@ -200,7 +232,13 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { article: formData.article, description: formData.description || undefined, price: formData.price, + pricePerSet: formData.pricePerSet || undefined, quantity: formData.quantity, + setQuantity: formData.setQuantity || undefined, + ordered: formData.ordered || undefined, + inTransit: formData.inTransit || undefined, + stock: formData.stock || undefined, + sold: formData.sold || undefined, type: formData.type, categoryId: formData.categoryId && formData.categoryId !== "none" @@ -241,7 +279,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { }; return ( -
+ {/* Основная информация */}

Основная информация

@@ -261,15 +299,38 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
- handleInputChange("article", e.target.value)} - placeholder="IP15PM-256-BLU" - className="glass-input text-white placeholder:text-white/40 h-10" - required - /> +
+ { + handleInputChange("article", e.target.value); + handleInputChange("autoGenerateArticle", false); // Отключаем автогенерацию при ручном вводе + }} + placeholder="SF-T-123456-001" + className="glass-input text-white placeholder:text-white/40 h-10 flex-1" + required + readOnly={formData.autoGenerateArticle} + /> + {!product && ( + + )} +
+ {formData.autoGenerateArticle && ( +

+ Артикул генерируется автоматически +

+ )}
@@ -309,7 +370,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {
+ + handleInputChange("pricePerSet", parseFloat(e.target.value) || 0) + } + placeholder="299999.99" + className="glass-input text-white placeholder:text-white/40 h-10" + /> +
+
+ +
+
+
+ +
+ + + handleInputChange("setQuantity", parseInt(e.target.value) || 0) + } + placeholder="10" + className="glass-input text-white placeholder:text-white/40 h-10" + /> +
diff --git a/src/components/warehouse/warehouse-dashboard.tsx b/src/components/warehouse/warehouse-dashboard.tsx index 7dfd067..ce5c302 100644 --- a/src/components/warehouse/warehouse-dashboard.tsx +++ b/src/components/warehouse/warehouse-dashboard.tsx @@ -15,8 +15,9 @@ import { Sidebar } from "@/components/dashboard/sidebar"; import { useSidebar } from "@/hooks/useSidebar"; import { ProductForm } from "./product-form"; import { ProductCard } from "./product-card"; +import { WarehouseStatistics } from "./warehouse-statistics"; import { GET_MY_PRODUCTS } from "@/graphql/queries"; -import { Plus, Search, Package } from "lucide-react"; +import { Plus, Package, Grid3X3, List, Edit3, Trash2 } from "lucide-react"; import { Input } from "@/components/ui/input"; interface Product { @@ -25,7 +26,13 @@ interface Product { article: string; description: string; price: number; + pricePerSet?: number; quantity: number; + setQuantity?: number; + ordered?: number; + inTransit?: number; + stock?: number; + sold?: number; type: "PRODUCT" | "CONSUMABLE"; category: { id: string; name: string } | null; brand: string; @@ -46,6 +53,7 @@ export function WarehouseDashboard() { const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingProduct, setEditingProduct] = useState(null); const [searchQuery, setSearchQuery] = useState(""); + const [viewMode, setViewMode] = useState<'cards' | 'table'>('cards'); const { data, loading, error, refetch } = useQuery(GET_MY_PRODUCTS, { errorPolicy: "all", @@ -53,24 +61,6 @@ export function WarehouseDashboard() { const products: Product[] = data?.myProducts || []; - // Отладочное логирование - React.useEffect(() => { - console.log("🏪 WAREHOUSE DASHBOARD DEBUG:", { - loading, - error: error?.message, - dataReceived: !!data, - productsCount: products.length, - products: products.map((p) => ({ - id: p.id, - name: p.name, - article: p.article, - type: p.type, - isActive: p.isActive, - createdAt: p.createdAt, - })), - }); - }, [data, loading, error, products]); - // Фильтрация товаров по поисковому запросу const filteredProducts = products.filter( (product) => @@ -140,61 +130,85 @@ export function WarehouseDashboard() { className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`} >
- {/* Заголовок и поиск */} + {/* Поиск и управление */}
-
-

Мой склад

-

- Управление товарами и расходниками -

-
+
+
+ setSearchQuery(e.target.value)} + className="glass-input text-white placeholder:text-white/50 h-10" + /> +
-
- - - - - - - - {editingProduct - ? "Редактировать товар/расходник" - : "Добавить товар/расходник"} - - + {/* Переключатель режимов отображения */} +
+ + +
+
+ + + + + + + + + {editingProduct + ? "Редактировать товар/расходник" + : "Добавить товар/расходник"} + + +
setIsDialogOpen(false)} /> - -
-
+
+ +
- {/* Поиск */} -
-
- - setSearchQuery(e.target.value)} - className="glass-input text-white placeholder:text-white/50 pl-10 h-10" - /> -
-
+ {/* Блок статистики */} + + + {/* Основной контент */} - - {loading ? ( + +{loading ? (
@@ -225,17 +239,106 @@ export function WarehouseDashboard() {
) : ( -
-
- {filteredProducts.map((product) => ( - - ))} -
+
+ {viewMode === 'cards' ? ( +
+ {filteredProducts.map((product) => ( + + ))} +
+ ) : ( +
+
+
Фото
+
Название
+
Артикул
+
Тип
+
Категория
+
Цена
+
Остаток
+
Заказано
+
В пути
+
Продано
+
Действия
+
+ {filteredProducts.map((product) => ( +
+
+ {product.mainImage || product.images[0] ? ( + {product.name} + ) : ( +
+ +
+ )} +
+
+
{product.name}
+
{product.brand}
+
+
{product.article}
+
+ + {product.type === 'PRODUCT' ? 'Товар' : 'Расходник'} + +
+
+ {product.category?.name || 'Нет'} +
+
+ {new Intl.NumberFormat('ru-RU', { + style: 'currency', + currency: 'RUB', + minimumFractionDigits: 0 + }).format(product.price)} +
+
+ + {product.stock || product.quantity || 0} + +
+
{product.ordered || 0}
+
{product.inTransit || 0}
+
{product.sold || 0}
+
+
+ + +
+
+
+ ))} +
+ )}
)} diff --git a/src/components/warehouse/warehouse-statistics.tsx b/src/components/warehouse/warehouse-statistics.tsx new file mode 100644 index 0000000..5071d80 --- /dev/null +++ b/src/components/warehouse/warehouse-statistics.tsx @@ -0,0 +1,191 @@ +"use client"; + +import React from "react"; +import { Card } from "@/components/ui/card"; +import { Badge } from "@/components/ui/badge"; +import { + Package, + ShoppingCart, + Truck, + CheckCircle, + AlertTriangle, + TrendingUp, + TrendingDown +} from "lucide-react"; + +interface Product { + id: string; + name: string; + article: string; + type: "PRODUCT" | "CONSUMABLE"; + quantity: number; + ordered?: number; + inTransit?: number; + stock?: number; + sold?: number; + isActive: boolean; +} + +interface WarehouseStatisticsProps { + products: Product[]; +} + +export function WarehouseStatistics({ products }: WarehouseStatisticsProps) { + console.log('📊 STATISTICS DEBUG:', { productsCount: products.length, products }); + + // Разделение товаров по типам + const goods = products.filter(p => p.type === 'PRODUCT'); + const consumables = products.filter(p => p.type === 'CONSUMABLE'); + + // Общая статистика + const totalProducts = products.length; + const activeProducts = products.filter(p => p.isActive).length; + const totalStock = products.reduce((sum, p) => sum + (p.stock || p.quantity || 0), 0); + const totalOrdered = products.reduce((sum, p) => sum + (p.ordered || 0), 0); + const totalInTransit = products.reduce((sum, p) => sum + (p.inTransit || 0), 0); + const totalSold = products.reduce((sum, p) => sum + (p.sold || 0), 0); + + // Статистика по товарам + const goodsStock = goods.reduce((sum, p) => sum + (p.stock || p.quantity || 0), 0); + const goodsOrdered = goods.reduce((sum, p) => sum + (p.ordered || 0), 0); + const goodsInTransit = goods.reduce((sum, p) => sum + (p.inTransit || 0), 0); + const goodsSold = goods.reduce((sum, p) => sum + (p.sold || 0), 0); + + // Статистика по расходникам + const consumablesStock = consumables.reduce((sum, p) => sum + (p.stock || p.quantity || 0), 0); + const consumablesOrdered = consumables.reduce((sum, p) => sum + (p.ordered || 0), 0); + const consumablesInTransit = consumables.reduce((sum, p) => sum + (p.inTransit || 0), 0); + const consumablesSold = consumables.reduce((sum, p) => sum + (p.sold || 0), 0); + + // Товары с низкими остатками + const lowStockProducts = products.filter(p => { + const stock = p.stock || p.quantity || 0; + return stock > 0 && stock < 10; + }); + + const outOfStockProducts = products.filter(p => { + const stock = p.stock || p.quantity || 0; + return stock === 0; + }); + + const StatCard = ({ + icon: Icon, + title, + value, + subtitle, + trend, + color = "text-white" + }: { + icon: any; + title: string; + value: number; + subtitle?: string; + trend?: 'up' | 'down'; + color?: string; + }) => ( + +
+
+
+ + {title} +
+
{value.toLocaleString()}
+ {subtitle && ( +
{subtitle}
+ )} +
+ {trend && ( +
+ {trend === 'up' ? ( + + ) : ( + + )} +
+ )} +
+
+ ); + + return ( +
+ {/* Общая статистика */} +
+ + + + +
+ + {/* Статистика по типам */} +
+ {/* Товары */} +
+
+ +

Товары

+ + {goods.length} + +
+
+
+
Остаток
+
{goodsStock}
+
+
+
Продано
+
{goodsSold}
+
+
+
+ + {/* Расходники */} +
+
+ +

Расходники

+ + {consumables.length} + +
+
+
+
Остаток
+
{consumablesStock}
+
+
+
Продано
+
{consumablesSold}
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/graphql/mutations.ts b/src/graphql/mutations.ts index b2205ab..2cded69 100644 --- a/src/graphql/mutations.ts +++ b/src/graphql/mutations.ts @@ -803,7 +803,13 @@ export const CREATE_PRODUCT = gql` article description price + pricePerSet quantity + setQuantity + ordered + inTransit + stock + sold type category { id @@ -836,7 +842,13 @@ export const UPDATE_PRODUCT = gql` article description price + pricePerSet quantity + setQuantity + ordered + inTransit + stock + sold type category { id diff --git a/src/graphql/queries.ts b/src/graphql/queries.ts index 5927b6e..8ae7168 100644 --- a/src/graphql/queries.ts +++ b/src/graphql/queries.ts @@ -134,7 +134,13 @@ export const GET_MY_PRODUCTS = gql` article description price + pricePerSet quantity + setQuantity + ordered + inTransit + stock + sold type category { id diff --git a/src/graphql/typedefs.ts b/src/graphql/typedefs.ts index 2eb7da3..31f3de4 100644 --- a/src/graphql/typedefs.ts +++ b/src/graphql/typedefs.ts @@ -696,7 +696,13 @@ export const typeDefs = gql` article: String! description: String price: Float! + pricePerSet: Float quantity: Int! + setQuantity: Int + ordered: Int + inTransit: Int + stock: Int + sold: Int type: ProductType category: Category brand: String @@ -718,7 +724,13 @@ export const typeDefs = gql` article: String! description: String price: Float! + pricePerSet: Float quantity: Int! + setQuantity: Int + ordered: Int + inTransit: Int + stock: Int + sold: Int type: ProductType categoryId: ID brand: String From 52881cf3024279b5bc49ee058f039278cc53a183 Mon Sep 17 00:00:00 2001 From: Bivekich Date: Fri, 1 Aug 2025 11:28:28 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B2=20=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D1=8C=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4=D1=83=D0=BA=D1=82=D0=B0=20=D0=B8=20?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D1=83=20=D0=B4=D0=BB=D1=8F=20=D1=83?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D0=B4=D0=BE=D0=BC:=20=D1=86=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=82,=20=D0=BA=D0=BE=D0=BB=D0=B8=D1=87=D0=B5=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=BE=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BB=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D0=B2,=20=D0=B0=20=D1=82=D0=B0=D0=BA=D0=B6=D0=B5?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8F=20=D0=B4=D0=BB=D1=8F=20=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D1=82=D0=B0=20=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80=D0=BE=D0=B2=20(=D0=B7?= =?UTF-8?q?=D0=B0=D0=BA=D0=B0=D0=B7=D0=B0=D0=BD=D0=BE,=20=D0=B2=20=D0=BF?= =?UTF-8?q?=D1=83=D1=82=D0=B8,=20=D0=BE=D1=81=D1=82=D0=B0=D1=82=D0=BE?= =?UTF-8?q?=D0=BA,=20=D0=BF=D1=80=D0=BE=D0=B4=D0=B0=D0=BD=D0=BE).=20=D0=9E?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D1=8B=20GraphQL=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=D0=BE=D0=BB=D0=B2=D0=B5=D1=80=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=BE=D0=B2=D1=8B=D1=85=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=D0=B9.=20=D0=9E=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=20=D0=B8=D0=BD=D1=82=D0=B5?= =?UTF-8?q?=D1=80=D1=84=D0=B5=D0=B9=D1=81=20=D1=84=D0=BE=D1=80=D0=BC=D1=8B?= =?UTF-8?q?=20=D0=B4=D0=BB=D1=8F=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=BB=D1=8C=D1=81=D0=BA=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=8B=D1=82=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prisma/schema.prisma | 6 + src/components/warehouse/product-form.tsx | 687 +++++++++--------- .../warehouse/warehouse-dashboard.tsx | 16 +- src/graphql/resolvers.ts | 24 + 4 files changed, 366 insertions(+), 367 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6cbcbba..9475f65 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -236,7 +236,13 @@ model Product { article String description String? price Decimal @db.Decimal(12, 2) + pricePerSet Decimal? @db.Decimal(12, 2) quantity Int @default(0) + setQuantity Int? + ordered Int? + inTransit Int? + stock Int? + sold Int? type ProductType @default(PRODUCT) categoryId String? brand String? diff --git a/src/components/warehouse/product-form.tsx b/src/components/warehouse/product-form.tsx index dc3c395..ce6c007 100644 --- a/src/components/warehouse/product-form.tsx +++ b/src/components/warehouse/product-form.tsx @@ -25,7 +25,13 @@ interface Product { article: string; description: string; price: number; + pricePerSet?: number; quantity: number; + setQuantity?: number; + ordered?: number; + inTransit?: number; + stock?: number; + sold?: number; type: "PRODUCT" | "CONSUMABLE"; category: { id: string; name: string } | null; brand: string; @@ -226,6 +232,8 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { return; } + console.log("📝 ФОРМА ДАННЫЕ ПЕРЕД ОТПРАВКОЙ:", formData); + try { const input = { name: formData.name, @@ -256,10 +264,12 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { }; if (product) { - await updateProduct({ + console.log("📝 ОБНОВЛЕНИЕ ТОВАРА - ОТПРАВКА ЗАПРОСА:", input); + const result = await updateProduct({ variables: { id: product.id, input }, refetchQueries: ["GetMyProducts"], }); + console.log("📝 РЕЗУЛЬТАТ ОБНОВЛЕНИЯ ТОВАРА:", result); toast.success("Товар успешно обновлен"); } else { console.log("📝 СОЗДАНИЕ ТОВАРА - ОТПРАВКА ЗАПРОСА:", input); @@ -279,380 +289,341 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { }; return ( - - {/* Основная информация */} - -

Основная информация

-
-
- - handleInputChange("name", e.target.value)} - placeholder="iPhone 15 Pro Max" - className="glass-input text-white placeholder:text-white/40 h-10" - required - /> -
+ + {/* Верхняя часть - 2 колонки */} +
+ {/* Левая колонка */} +
+ {/* Основная информация */} + +

Основная информация

+
+
+ + handleInputChange("name", e.target.value)} + placeholder="iPhone 15 Pro Max" + className="glass-input text-white placeholder:text-white/40 h-8 text-sm" + required + /> +
-
- -
- { - handleInputChange("article", e.target.value); - handleInputChange("autoGenerateArticle", false); // Отключаем автогенерацию при ручном вводе - }} - placeholder="SF-T-123456-001" - className="glass-input text-white placeholder:text-white/40 h-10 flex-1" - required - readOnly={formData.autoGenerateArticle} - /> - {!product && ( - - )} -
- {formData.autoGenerateArticle && ( -

- Артикул генерируется автоматически -

- )} -
-
- -
- -