diff --git a/diagnostic-script.js b/diagnostic-script.js new file mode 100644 index 0000000..575bb70 --- /dev/null +++ b/diagnostic-script.js @@ -0,0 +1,96 @@ +const { PrismaClient } = require("@prisma/client"); + +const prisma = new PrismaClient(); + +async function diagnoseDatabase() { + try { + console.log("🔍 ДИАГНОСТИКА БАЗЫ ДАННЫХ...\n"); + + // Проверяем пользователей + const users = await prisma.user.findMany({ + include: { + organization: true, + }, + }); + + console.log("👥 ПОЛЬЗОВАТЕЛИ:"); + users.forEach((user) => { + console.log(` - ID: ${user.id}`); + console.log(` Телефон: ${user.phone}`); + console.log(` Организация: ${user.organization?.name || "НЕТ"}`); + console.log(` Тип организации: ${user.organization?.type || "НЕТ"}`); + console.log(""); + }); + + // Проверяем организации + const organizations = await prisma.organization.findMany(); + console.log("🏢 ОРГАНИЗАЦИИ:"); + organizations.forEach((org) => { + console.log(` - ID: ${org.id}`); + console.log(` Название: ${org.name}`); + console.log(` Тип: ${org.type}`); + console.log(""); + }); + + // Проверяем товары + const products = await prisma.product.findMany({ + include: { + organization: true, + category: true, + }, + orderBy: { + createdAt: "desc", + }, + }); + + console.log("🛍️ ТОВАРЫ:"); + if (products.length === 0) { + console.log(" НЕТ ТОВАРОВ В БАЗЕ ДАННЫХ"); + } else { + products.forEach((product) => { + console.log(` - ID: ${product.id}`); + console.log(` Название: ${product.name}`); + console.log(` Артикул: ${product.article}`); + console.log(` Тип: ${product.type}`); + console.log(` Активен: ${product.isActive}`); + console.log( + ` Организация: ${product.organization?.name || "НЕТ"} (${ + product.organization?.type || "НЕТ" + })` + ); + console.log(` Создан: ${product.createdAt}`); + console.log(""); + }); + } + + // Проверяем товары поставщиков + const wholesaleProducts = await prisma.product.findMany({ + where: { + organization: { + type: "WHOLESALE", + }, + type: "PRODUCT", + }, + include: { + organization: true, + }, + }); + + console.log("🏪 ТОВАРЫ ПОСТАВЩИКОВ (WHOLESALE + PRODUCT):"); + if (wholesaleProducts.length === 0) { + console.log(" НЕТ ТОВАРОВ ПОСТАВЩИКОВ"); + } else { + wholesaleProducts.forEach((product) => { + console.log( + ` - ${product.name} (${product.article}) - ${product.organization?.name}` + ); + }); + } + } catch (error) { + console.error("❌ ОШИБКА:", error); + } finally { + await prisma.$disconnect(); + } +} + +diagnoseDatabase(); diff --git a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx index ddab942..f89415e 100644 --- a/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx +++ b/src/components/fulfillment-supplies/create-fulfillment-consumables-supply-page.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useQuery, useMutation } from "@apollo/client"; import { Sidebar } from "@/components/dashboard/sidebar"; @@ -54,6 +54,7 @@ interface FulfillmentConsumableProduct { name: string; description?: string; price: number; + type?: "PRODUCT" | "CONSUMABLE"; category?: { name: string }; images: string[]; mainImage?: string; @@ -128,6 +129,36 @@ export function CreateFulfillmentConsumablesSupplyPage() { ) : []; + // Отладочное логирование + React.useEffect(() => { + console.log("🛒 FULFILLMENT CONSUMABLES DEBUG:", { + selectedSupplier: selectedSupplier + ? { + id: selectedSupplier.id, + name: selectedSupplier.name || selectedSupplier.fullName, + type: selectedSupplier.type, + } + : null, + productsLoading, + allProductsCount: productsData?.allProducts?.length || 0, + supplierProductsCount: supplierProducts.length, + allProducts: + productsData?.allProducts?.map((p) => ({ + id: p.id, + name: p.name, + organizationId: p.organization.id, + organizationName: p.organization.name, + type: p.type || "NO_TYPE", + })) || [], + supplierProducts: supplierProducts.map((p) => ({ + id: p.id, + name: p.name, + organizationId: p.organization.id, + organizationName: p.organization.name, + })), + }); + }, [selectedSupplier, productsData, productsLoading, supplierProducts]); + const formatCurrency = (amount: number) => { return new Intl.NumberFormat("ru-RU", { style: "currency", @@ -363,7 +394,14 @@ export function CreateFulfillmentConsumablesSupplyPage() { width: "calc((100% - 48px) / 7)", // 48px = 6 gaps * 8px each animationDelay: `${index * 100}ms`, }} - onClick={() => setSelectedSupplier(supplier)} + onClick={() => { + console.log("🔄 ВЫБРАН ПОСТАВЩИК:", { + id: supplier.id, + name: supplier.name || supplier.fullName, + type: supplier.type, + }); + setSelectedSupplier(supplier); + }} >
diff --git a/src/components/warehouse/product-form.tsx b/src/components/warehouse/product-form.tsx index 9a1611d..02a69d5 100644 --- a/src/components/warehouse/product-form.tsx +++ b/src/components/warehouse/product-form.tsx @@ -1,181 +1,197 @@ -"use client" +"use client"; -import { useState, useRef } from 'react' -import Image from 'next/image' -import { useMutation, useQuery } from '@apollo/client' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -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 { toast } from 'sonner' +import { useState, useRef } from "react"; +import Image from "next/image"; +import { useMutation, useQuery } from "@apollo/client"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +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 { toast } from "sonner"; interface Product { - id: string - name: string - article: string - description: string - price: number - quantity: number - type: 'PRODUCT' | 'CONSUMABLE' - category: { id: string; name: string } | null - brand: string - color: string - size: string - weight: number - dimensions: string - material: string - images: string[] - mainImage: string - isActive: boolean + id: string; + name: string; + article: string; + description: string; + price: number; + quantity: number; + type: "PRODUCT" | "CONSUMABLE"; + category: { id: string; name: string } | null; + brand: string; + color: string; + size: string; + weight: number; + dimensions: string; + material: string; + images: string[]; + mainImage: string; + isActive: boolean; } interface ProductFormProps { - product?: Product | null - onSave: () => void - onCancel: () => void + product?: Product | null; + onSave: () => void; + onCancel: () => void; } export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { const [formData, setFormData] = useState({ - name: product?.name || '', - article: product?.article || '', - description: product?.description || '', + name: product?.name || "", + article: product?.article || "", + description: product?.description || "", price: product?.price || 0, quantity: product?.quantity || 0, - type: product?.type || 'PRODUCT' as 'PRODUCT' | 'CONSUMABLE', - categoryId: product?.category?.id || 'none', - brand: product?.brand || '', - color: product?.color || '', - size: product?.size || '', + type: product?.type || ("PRODUCT" as "PRODUCT" | "CONSUMABLE"), + categoryId: product?.category?.id || "none", + brand: product?.brand || "", + color: product?.color || "", + size: product?.size || "", weight: product?.weight || 0, - dimensions: product?.dimensions || '', - material: product?.material || '', + dimensions: product?.dimensions || "", + material: product?.material || "", images: product?.images || [], - mainImage: product?.mainImage || '', - isActive: product?.isActive ?? true - }) + mainImage: product?.mainImage || "", + isActive: product?.isActive ?? true, + }); - const [isUploading] = useState(false) - const [uploadingImages, setUploadingImages] = useState>(new Set()) - const fileInputRef = useRef(null) + const [isUploading] = useState(false); + const [uploadingImages, setUploadingImages] = useState>( + new Set() + ); + const fileInputRef = useRef(null); + + const [createProduct, { loading: creating }] = useMutation(CREATE_PRODUCT); + const [updateProduct, { loading: updating }] = useMutation(UPDATE_PRODUCT); - const [createProduct, { loading: creating }] = useMutation(CREATE_PRODUCT) - const [updateProduct, { loading: updating }] = useMutation(UPDATE_PRODUCT) - // Загружаем категории - const { data: categoriesData } = useQuery(GET_CATEGORIES) + const { data: categoriesData } = useQuery(GET_CATEGORIES); - const loading = creating || updating + const loading = creating || updating; - const handleInputChange = (field: string, value: string | number | boolean) => { - setFormData(prev => ({ + const handleInputChange = ( + field: string, + value: string | number | boolean + ) => { + setFormData((prev) => ({ ...prev, - [field]: value - })) - } + [field]: value, + })); + }; const handleImageUpload = async (files: FileList) => { - const newUploadingIndexes = new Set() - const startIndex = formData.images.length + const newUploadingIndexes = new Set(); + const startIndex = formData.images.length; // Добавляем плейсхолдеры для загружаемых изображений const placeholders = Array.from(files).map((_, index) => { - newUploadingIndexes.add(startIndex + index) - return '' // Пустой URL как плейсхолдер - }) + newUploadingIndexes.add(startIndex + index); + return ""; // Пустой URL как плейсхолдер + }); - setUploadingImages(prev => new Set([...prev, ...newUploadingIndexes])) - setFormData(prev => ({ + setUploadingImages((prev) => new Set([...prev, ...newUploadingIndexes])); + setFormData((prev) => ({ ...prev, - images: [...prev.images, ...placeholders] - })) + images: [...prev.images, ...placeholders], + })); try { // Загружаем каждое изображение const uploadPromises = Array.from(files).map(async (file, index) => { - const actualIndex = startIndex + index - const formData = new FormData() - formData.append('file', file) - formData.append('type', 'product') + const actualIndex = startIndex + index; + const formData = new FormData(); + formData.append("file", file); + formData.append("type", "product"); - const response = await fetch('/api/upload-file', { - method: 'POST', - body: formData - }) + const response = await fetch("/api/upload-file", { + method: "POST", + body: formData, + }); if (!response.ok) { - throw new Error('Ошибка загрузки изображения') + throw new Error("Ошибка загрузки изображения"); } - const result = await response.json() - return { index: actualIndex, url: result.fileUrl } - }) + const result = await response.json(); + return { index: actualIndex, url: result.fileUrl }; + }); - const results = await Promise.all(uploadPromises) + const results = await Promise.all(uploadPromises); // Обновляем URLs загруженных изображений - setFormData(prev => { - const newImages = [...prev.images] + setFormData((prev) => { + const newImages = [...prev.images]; results.forEach(({ index, url }) => { - newImages[index] = url - }) + newImages[index] = url; + }); return { ...prev, images: newImages, - mainImage: prev.mainImage || results[0]?.url || '' // Устанавливаем первое изображение как главное - } - }) + mainImage: prev.mainImage || results[0]?.url || "", // Устанавливаем первое изображение как главное + }; + }); - toast.success('Изображения успешно загружены') + toast.success("Изображения успешно загружены"); } catch (error) { - console.error('Error uploading images:', error) - toast.error('Ошибка загрузки изображений') - + console.error("Error uploading images:", error); + toast.error("Ошибка загрузки изображений"); + // Удаляем неудачные плейсхолдеры - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - images: prev.images.slice(0, startIndex) - })) + images: prev.images.slice(0, startIndex), + })); } finally { // Убираем индикаторы загрузки - setUploadingImages(prev => { - const updated = new Set(prev) - newUploadingIndexes.forEach(index => updated.delete(index)) - return updated - }) + setUploadingImages((prev) => { + const updated = new Set(prev); + newUploadingIndexes.forEach((index) => updated.delete(index)); + return updated; + }); } - } + }; const handleRemoveImage = (indexToRemove: number) => { - setFormData(prev => { - const newImages = prev.images.filter((_, index) => index !== indexToRemove) - const removedImageUrl = prev.images[indexToRemove] - + setFormData((prev) => { + const newImages = prev.images.filter( + (_, index) => index !== indexToRemove + ); + const removedImageUrl = prev.images[indexToRemove]; + return { ...prev, images: newImages, - mainImage: prev.mainImage === removedImageUrl ? (newImages[0] || '') : prev.mainImage - } - }) - } + mainImage: + prev.mainImage === removedImageUrl + ? newImages[0] || "" + : prev.mainImage, + }; + }); + }; const handleSetMainImage = (imageUrl: string) => { - setFormData(prev => ({ + setFormData((prev) => ({ ...prev, - mainImage: imageUrl - })) - } + mainImage: imageUrl, + })); + }; const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() + e.preventDefault(); if (!formData.name || !formData.article || formData.price <= 0) { - toast.error('Пожалуйста, заполните все обязательные поля') - return + toast.error("Пожалуйста, заполните все обязательные поля"); + return; } try { @@ -186,36 +202,43 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { price: formData.price, quantity: formData.quantity, type: formData.type, - categoryId: formData.categoryId && formData.categoryId !== 'none' ? formData.categoryId : undefined, + categoryId: + formData.categoryId && formData.categoryId !== "none" + ? formData.categoryId + : undefined, brand: formData.brand || undefined, color: formData.color || undefined, size: formData.size || undefined, weight: formData.weight || undefined, dimensions: formData.dimensions || undefined, material: formData.material || undefined, - images: formData.images.filter(img => img), // Убираем пустые строки + images: formData.images.filter((img) => img), // Убираем пустые строки mainImage: formData.mainImage || undefined, - isActive: formData.isActive - } + isActive: formData.isActive, + }; if (product) { await updateProduct({ - variables: { id: product.id, input } - }) - toast.success('Товар успешно обновлен') + variables: { id: product.id, input }, + refetchQueries: ["GetMyProducts"], + }); + toast.success("Товар успешно обновлен"); } else { - await createProduct({ - variables: { input } - }) - toast.success('Товар успешно создан') + console.log("📝 СОЗДАНИЕ ТОВАРА - ОТПРАВКА ЗАПРОСА:", input); + const result = await createProduct({ + variables: { input }, + refetchQueries: ["GetMyProducts"], + }); + console.log("📝 РЕЗУЛЬТАТ СОЗДАНИЯ ТОВАРА:", result); + toast.success("Товар успешно создан"); } - onSave() + onSave(); } catch (error: unknown) { - console.error('Error saving product:', error) - toast.error((error as Error).message || 'Ошибка при сохранении товара') + console.error("Error saving product:", error); + toast.error((error as Error).message || "Ошибка при сохранении товара"); } - } + }; return (
@@ -229,20 +252,20 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) { handleInputChange('name', e.target.value)} + onChange={(e) => handleInputChange("name", e.target.value)} placeholder="iPhone 15 Pro Max" className="glass-input text-white placeholder:text-white/40 h-10" required />
- +
handleInputChange('article', e.target.value)} + onChange={(e) => handleInputChange("article", e.target.value)} placeholder="IP15PM-256-BLU" className="glass-input text-white placeholder:text-white/40 h-10" required @@ -254,7 +277,7 @@ export function ProductForm({ product, onSave, onCancel }: ProductFormProps) {