Files
sfera-new/src/components/supplies/consumables-supplies/consumables-supplies-tab.tsx

400 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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>
);
}