247 lines
8.9 KiB
TypeScript
247 lines
8.9 KiB
TypeScript
"use client";
|
||
|
||
import React, { useState } from "react";
|
||
import { useQuery } from "@apollo/client";
|
||
import { Card } from "@/components/ui/card";
|
||
import { Button } from "@/components/ui/button";
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from "@/components/ui/dialog";
|
||
import { Sidebar } from "@/components/dashboard/sidebar";
|
||
import { useSidebar } from "@/hooks/useSidebar";
|
||
import { ProductForm } from "./product-form";
|
||
import { ProductCard } from "./product-card";
|
||
import { GET_MY_PRODUCTS } from "@/graphql/queries";
|
||
import { Plus, Search, Package } from "lucide-react";
|
||
import { Input } from "@/components/ui/input";
|
||
|
||
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;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
}
|
||
|
||
export function WarehouseDashboard() {
|
||
const { getSidebarMargin } = useSidebar();
|
||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
|
||
const [searchQuery, setSearchQuery] = useState("");
|
||
|
||
const { data, loading, error, refetch } = useQuery(GET_MY_PRODUCTS, {
|
||
errorPolicy: "all",
|
||
});
|
||
|
||
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) =>
|
||
product.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||
product.article.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||
product.category?.name
|
||
?.toLowerCase()
|
||
.includes(searchQuery.toLowerCase()) ||
|
||
product.brand?.toLowerCase().includes(searchQuery.toLowerCase())
|
||
);
|
||
|
||
const handleCreateProduct = () => {
|
||
setEditingProduct(null);
|
||
setIsDialogOpen(true);
|
||
};
|
||
|
||
const handleEditProduct = (product: Product) => {
|
||
setEditingProduct(product);
|
||
setIsDialogOpen(true);
|
||
};
|
||
|
||
const handleProductSaved = () => {
|
||
setIsDialogOpen(false);
|
||
setEditingProduct(null);
|
||
refetch();
|
||
};
|
||
|
||
const handleProductDeleted = () => {
|
||
refetch();
|
||
};
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="h-screen flex overflow-hidden">
|
||
<Sidebar />
|
||
<main className="flex-1 ml-56 px-6 py-4 overflow-hidden">
|
||
<div className="h-full w-full flex flex-col">
|
||
<Card className="flex-1 bg-white/5 backdrop-blur border-white/10 p-6">
|
||
<div className="flex items-center justify-center h-full">
|
||
<div className="text-center">
|
||
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
|
||
<h3 className="text-lg font-medium text-white mb-2">
|
||
Ошибка загрузки
|
||
</h3>
|
||
<p className="text-white/60 text-sm mb-4">
|
||
{error.message || "Не удалось загрузить товары"}
|
||
</p>
|
||
<Button
|
||
onClick={() => refetch()}
|
||
className="bg-purple-600 hover:bg-purple-700 text-white"
|
||
>
|
||
Попробовать снова
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="h-screen flex overflow-hidden">
|
||
<Sidebar />
|
||
<main
|
||
className={`flex-1 ${getSidebarMargin()} px-6 py-4 overflow-hidden transition-all duration-300`}
|
||
>
|
||
<div className="h-full w-full flex flex-col">
|
||
{/* Заголовок и поиск */}
|
||
<div className="flex items-center justify-between mb-4 flex-shrink-0">
|
||
<div>
|
||
<h1 className="text-xl font-bold text-white mb-1">Мой склад</h1>
|
||
<p className="text-white/70 text-sm">
|
||
Управление товарами и расходниками
|
||
</p>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||
<DialogTrigger asChild>
|
||
<Button
|
||
onClick={handleCreateProduct}
|
||
className="bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-700 hover:to-pink-700 text-white border-0 shadow-lg shadow-purple-500/25 transition-all duration-300"
|
||
>
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
Добавить товар/расходник
|
||
</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="glass-card max-w-4xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-white">
|
||
{editingProduct
|
||
? "Редактировать товар/расходник"
|
||
: "Добавить товар/расходник"}
|
||
</DialogTitle>
|
||
</DialogHeader>
|
||
<ProductForm
|
||
product={editingProduct}
|
||
onSave={handleProductSaved}
|
||
onCancel={() => setIsDialogOpen(false)}
|
||
/>
|
||
</DialogContent>
|
||
</Dialog>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Поиск */}
|
||
<div className="mb-4 flex-shrink-0">
|
||
<div className="relative max-w-md">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-white/50" />
|
||
<Input
|
||
type="text"
|
||
placeholder="Поиск по названию, артикулу, категории..."
|
||
value={searchQuery}
|
||
onChange={(e) => setSearchQuery(e.target.value)}
|
||
className="glass-input text-white placeholder:text-white/50 pl-10 h-10"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Основной контент */}
|
||
<Card className="flex-1 bg-white/5 backdrop-blur border-white/10 p-6 overflow-hidden">
|
||
{loading ? (
|
||
<div className="flex items-center justify-center h-full">
|
||
<div className="text-center">
|
||
<div className="animate-spin rounded-full h-16 w-16 border-4 border-white border-t-transparent mx-auto mb-4"></div>
|
||
<p className="text-white/70">Загрузка товаров...</p>
|
||
</div>
|
||
</div>
|
||
) : filteredProducts.length === 0 ? (
|
||
<div className="flex items-center justify-center h-full">
|
||
<div className="text-center">
|
||
<Package className="h-16 w-16 text-white/40 mx-auto mb-4" />
|
||
<h3 className="text-lg font-medium text-white mb-2">
|
||
{searchQuery ? "Товары не найдены" : "Склад пуст"}
|
||
</h3>
|
||
<p className="text-white/60 text-sm mb-4">
|
||
{searchQuery
|
||
? "Попробуйте изменить критерии поиска"
|
||
: "Добавьте ваш первый товар на склад"}
|
||
</p>
|
||
{!searchQuery && (
|
||
<Button
|
||
onClick={handleCreateProduct}
|
||
className="bg-purple-600 hover:bg-purple-700 text-white"
|
||
>
|
||
<Plus className="w-4 h-4 mr-2" />
|
||
Добавить товар
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<div className="h-full overflow-y-auto">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
|
||
{filteredProducts.map((product) => (
|
||
<ProductCard
|
||
key={product.id}
|
||
product={product}
|
||
onEdit={handleEditProduct}
|
||
onDeleted={handleProductDeleted}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|