400 lines
15 KiB
TypeScript
400 lines
15 KiB
TypeScript
"use client";
|
||
|
||
import React, { useState } from "react";
|
||
import { useQuery } from "@apollo/client";
|
||
import { Card } from "@/components/ui/card";
|
||
import { Badge } from "@/components/ui/badge";
|
||
import { useAuth } from "@/hooks/useAuth";
|
||
import {
|
||
ChevronDown,
|
||
ChevronRight,
|
||
Calendar,
|
||
Package,
|
||
TrendingUp,
|
||
DollarSign,
|
||
Box,
|
||
} from "lucide-react";
|
||
import { GET_SUPPLY_ORDERS } from "@/graphql/queries";
|
||
|
||
// Типы данных для заказов поставок расходников
|
||
interface SupplyOrderItem {
|
||
id: string;
|
||
quantity: number;
|
||
price: number;
|
||
totalPrice: number;
|
||
product: {
|
||
id: string;
|
||
name: string;
|
||
article?: string;
|
||
description?: string;
|
||
category?: {
|
||
id: string;
|
||
name: string;
|
||
};
|
||
};
|
||
}
|
||
|
||
interface SupplyOrder {
|
||
id: string;
|
||
deliveryDate: string;
|
||
status: "PENDING" | "CONFIRMED" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";
|
||
totalAmount: number;
|
||
totalItems: number;
|
||
createdAt: string;
|
||
updatedAt: string;
|
||
partner: {
|
||
id: string;
|
||
name?: string;
|
||
fullName?: string;
|
||
inn?: string;
|
||
address?: string;
|
||
phones?: string[];
|
||
emails?: string[];
|
||
};
|
||
organization: {
|
||
id: string;
|
||
name?: string;
|
||
fullName?: string;
|
||
type: string;
|
||
};
|
||
items: SupplyOrderItem[];
|
||
}
|
||
|
||
export function SuppliesConsumablesTab() {
|
||
const [expandedOrders, setExpandedOrders] = useState<Set<string>>(new Set());
|
||
const { user } = useAuth();
|
||
|
||
// Загружаем заказы поставок
|
||
const { data, loading, error } = useQuery(GET_SUPPLY_ORDERS, {
|
||
fetchPolicy: "cache-and-network", // Всегда проверяем актуальные данные
|
||
});
|
||
|
||
const toggleOrderExpansion = (orderId: string) => {
|
||
const newExpanded = new Set(expandedOrders);
|
||
if (newExpanded.has(orderId)) {
|
||
newExpanded.delete(orderId);
|
||
} else {
|
||
newExpanded.add(orderId);
|
||
}
|
||
setExpandedOrders(newExpanded);
|
||
};
|
||
|
||
// Получаем данные заказов поставок и фильтруем только заказы созданные текущим селлером
|
||
const allSupplyOrders: SupplyOrder[] = data?.supplyOrders || [];
|
||
const supplyOrders: SupplyOrder[] = allSupplyOrders.filter(
|
||
(order) => order.organization.id === user?.organization?.id
|
||
);
|
||
|
||
// Генерируем порядковые номера для заказов
|
||
const ordersWithNumbers = supplyOrders.map((order, index) => ({
|
||
...order,
|
||
number: supplyOrders.length - index, // Обратный порядок для новых заказов сверху
|
||
}));
|
||
|
||
const getStatusBadge = (status: SupplyOrder["status"]) => {
|
||
const statusMap = {
|
||
PENDING: {
|
||
label: "Ожидание",
|
||
color: "bg-blue-500/20 text-blue-300 border-blue-500/30",
|
||
},
|
||
CONFIRMED: {
|
||
label: "Подтверждена",
|
||
color: "bg-green-500/20 text-green-300 border-green-500/30",
|
||
},
|
||
IN_TRANSIT: {
|
||
label: "В пути",
|
||
color: "bg-yellow-500/20 text-yellow-300 border-yellow-500/30",
|
||
},
|
||
DELIVERED: {
|
||
label: "Доставлена",
|
||
color: "bg-purple-500/20 text-purple-300 border-purple-500/30",
|
||
},
|
||
CANCELLED: {
|
||
label: "Отменена",
|
||
color: "bg-red-500/20 text-red-300 border-red-500/30",
|
||
},
|
||
};
|
||
const { label, color } = statusMap[status];
|
||
return <Badge className={`${color} border`}>{label}</Badge>;
|
||
};
|
||
|
||
const formatCurrency = (amount: number) => {
|
||
return new Intl.NumberFormat("ru-RU", {
|
||
style: "currency",
|
||
currency: "RUB",
|
||
minimumFractionDigits: 0,
|
||
}).format(amount);
|
||
};
|
||
|
||
const formatDate = (dateString: string) => {
|
||
return new Date(dateString).toLocaleDateString("ru-RU", {
|
||
day: "2-digit",
|
||
month: "2-digit",
|
||
year: "numeric",
|
||
});
|
||
};
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="flex items-center justify-center h-64">
|
||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-white border-t-transparent"></div>
|
||
<span className="ml-3 text-white">Загрузка заказов поставок...</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="text-center py-8">
|
||
<p className="text-red-400">Ошибка загрузки: {error.message}</p>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
{/* Статистика заказов поставок */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="p-2 bg-orange-500/20 rounded-lg">
|
||
<Box className="h-5 w-5 text-orange-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-white/60 text-xs">Заказов поставок</p>
|
||
<p className="text-xl font-bold text-white">
|
||
{supplyOrders.length}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="p-2 bg-green-500/20 rounded-lg">
|
||
<TrendingUp className="h-5 w-5 text-green-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-white/60 text-xs">Общая сумма</p>
|
||
<p className="text-xl font-bold text-white">
|
||
{formatCurrency(
|
||
supplyOrders.reduce(
|
||
(sum, order) => sum + Number(order.totalAmount),
|
||
0
|
||
)
|
||
)}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="p-2 bg-yellow-500/20 rounded-lg">
|
||
<Calendar className="h-5 w-5 text-yellow-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-white/60 text-xs">В пути</p>
|
||
<p className="text-xl font-bold text-white">
|
||
{
|
||
supplyOrders.filter((order) => order.status === "IN_TRANSIT")
|
||
.length
|
||
}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card className="bg-white/10 backdrop-blur border-white/20 p-4">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="p-2 bg-blue-500/20 rounded-lg">
|
||
<Calendar className="h-5 w-5 text-blue-400" />
|
||
</div>
|
||
<div>
|
||
<p className="text-white/60 text-xs">Доставлено</p>
|
||
<p className="text-xl font-bold text-white">
|
||
{
|
||
supplyOrders.filter((order) => order.status === "DELIVERED")
|
||
.length
|
||
}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Таблица заказов поставок */}
|
||
<Card className="bg-white/10 backdrop-blur border-white/20 overflow-hidden">
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full">
|
||
<thead>
|
||
<tr className="border-b border-white/20">
|
||
<th className="text-left p-4 text-white font-semibold">№</th>
|
||
<th className="text-left p-4 text-white font-semibold">
|
||
Поставщик
|
||
</th>
|
||
<th className="text-left p-4 text-white font-semibold">
|
||
Дата поставки
|
||
</th>
|
||
<th className="text-left p-4 text-white font-semibold">
|
||
Дата создания
|
||
</th>
|
||
<th className="text-left p-4 text-white font-semibold">
|
||
Товаров
|
||
</th>
|
||
<th className="text-left p-4 text-white font-semibold">
|
||
Сумма
|
||
</th>
|
||
<th className="text-left p-4 text-white font-semibold">
|
||
Статус
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{ordersWithNumbers.length === 0 ? (
|
||
<tr>
|
||
<td colSpan={7} className="text-center py-8">
|
||
<div className="text-white/60">
|
||
<Box className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||
<p>Заказов поставок пока нет</p>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
) : (
|
||
ordersWithNumbers.map((order) => {
|
||
const isOrderExpanded = expandedOrders.has(order.id);
|
||
|
||
return (
|
||
<React.Fragment key={order.id}>
|
||
{/* Основная строка заказа поставки */}
|
||
<tr
|
||
className="border-b border-white/10 hover:bg-white/5 transition-colors bg-orange-500/10 cursor-pointer"
|
||
onClick={() => toggleOrderExpansion(order.id)}
|
||
>
|
||
<td className="p-4">
|
||
<div className="flex items-center space-x-2">
|
||
{isOrderExpanded ? (
|
||
<ChevronDown className="h-4 w-4 text-white/60" />
|
||
) : (
|
||
<ChevronRight className="h-4 w-4 text-white/60" />
|
||
)}
|
||
<span className="text-white font-normal text-lg">
|
||
{order.number}
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td className="p-4">
|
||
<div className="text-white">
|
||
<div className="font-medium">
|
||
{order.partner.name ||
|
||
order.partner.fullName ||
|
||
"Поставщик"}
|
||
</div>
|
||
{order.partner.inn && (
|
||
<div className="text-xs text-white/60">
|
||
ИНН: {order.partner.inn}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</td>
|
||
<td className="p-4">
|
||
<div className="flex items-center space-x-2">
|
||
<Calendar className="h-4 w-4 text-white/40" />
|
||
<span className="text-white font-semibold">
|
||
{formatDate(order.deliveryDate)}
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td className="p-4">
|
||
<span className="text-white/80">
|
||
{formatDate(order.createdAt)}
|
||
</span>
|
||
</td>
|
||
<td className="p-4">
|
||
<span className="text-white font-semibold">
|
||
{order.totalItems}
|
||
</span>
|
||
</td>
|
||
<td className="p-4">
|
||
<div className="flex items-center space-x-2">
|
||
<DollarSign className="h-4 w-4 text-white/40" />
|
||
<span className="text-white font-bold text-lg">
|
||
{formatCurrency(Number(order.totalAmount))}
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td className="p-4">{getStatusBadge(order.status)}</td>
|
||
</tr>
|
||
|
||
{/* Развернутые детали заказа - товары */}
|
||
{isOrderExpanded &&
|
||
order.items.map((item) => (
|
||
<tr
|
||
key={item.id}
|
||
className="border-b border-white/10 hover:bg-white/5 transition-colors bg-blue-500/10"
|
||
>
|
||
<td className="p-4 pl-12">
|
||
<div className="flex items-center space-x-2">
|
||
<Package className="h-4 w-4 text-blue-400" />
|
||
<span className="text-white font-medium text-sm">
|
||
Товар
|
||
</span>
|
||
</div>
|
||
</td>
|
||
<td className="p-4" colSpan={2}>
|
||
<div className="text-white">
|
||
<div className="font-medium mb-1">
|
||
{item.product.name}
|
||
</div>
|
||
{item.product.article && (
|
||
<div className="text-xs text-white/60 mb-1">
|
||
Артикул: {item.product.article}
|
||
</div>
|
||
)}
|
||
{item.product.category && (
|
||
<Badge className="bg-gray-500/20 text-gray-300 border-gray-500/30 border text-xs">
|
||
{item.product.category.name}
|
||
</Badge>
|
||
)}
|
||
{item.product.description && (
|
||
<div className="text-xs text-white/60 mt-1">
|
||
{item.product.description}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</td>
|
||
<td className="p-4">
|
||
<span className="text-white/80 text-sm">
|
||
{formatDate(order.createdAt)}
|
||
</span>
|
||
</td>
|
||
<td className="p-4">
|
||
<span className="text-white font-semibold">
|
||
{item.quantity}
|
||
</span>
|
||
</td>
|
||
<td className="p-4">
|
||
<div className="text-white">
|
||
<div className="font-medium">
|
||
{formatCurrency(Number(item.totalPrice))}
|
||
</div>
|
||
<div className="text-xs text-white/60">
|
||
{formatCurrency(Number(item.price))} за шт.
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td className="p-4"></td>
|
||
</tr>
|
||
))}
|
||
</React.Fragment>
|
||
);
|
||
})
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|