Files
sfera-new/src/components/wb-warehouse/wb-warehouse-dashboard-refactored.tsx

344 lines
13 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, useEffect } from 'react'
import { useAuth } from '@/hooks/useAuth'
import { Sidebar } from '@/components/dashboard/sidebar'
import { useSidebar } from '@/hooks/useSidebar'
import { Card } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { WildberriesService } from '@/services/wildberries-service'
import { toast } from 'sonner'
import { StatsCards } from './stats-cards'
import { SearchBar } from './search-bar'
import { TableHeader } from './table-header'
import { LoadingSkeleton } from './loading-skeleton'
import { StockTableRow } from './stock-table-row'
import { TrendingUp, Package } from 'lucide-react'
interface WBStock {
nmId: number
vendorCode: string
title: string
brand: string
price: number
stocks: Array<{
warehouseId: number
warehouseName: string
quantity: number
quantityFull: number
inWayToClient: number
inWayFromClient: number
}>
totalQuantity: number
totalReserved: number
photos?: Array<{
big?: string
c246x328?: string
c516x688?: string
square?: string
tm?: string
}>
mediaFiles?: string[]
characteristics?: Array<{
name: string
value: string | string[]
}>
subjectName: string
description: string
}
interface WBWarehouse {
id: number
name: string
cargoType: number
deliveryType: number
}
export function WBWarehouseDashboard() {
const { user } = useAuth()
const { isCollapsed, getSidebarMargin } = useSidebar()
const [stocks, setStocks] = useState<WBStock[]>([])
const [warehouses, setWarehouses] = useState<WBWarehouse[]>([])
const [loading, setLoading] = useState(false)
const [searchTerm, setSearchTerm] = useState('')
// Статистика
const [totalProducts, setTotalProducts] = useState(0)
const [totalStocks, setTotalStocks] = useState(0)
const [totalReserved, setTotalReserved] = useState(0)
const [totalFromClient, setTotalFromClient] = useState(0)
const [activeWarehouses, setActiveWarehouses] = useState(0)
// Analytics data
const [analyticsData, setAnalyticsData] = useState<any[]>([])
const hasWBApiKey = user?.wildberriesApiKey
// Комбинирование карточек с индивидуальными данными аналитики
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const combineCardsWithIndividualAnalytics = (cards: any[], analyticsResults: any[]): WBStock[] => {
const stocksMap = new Map<number, WBStock>()
// Создаем карту аналитических данных для быстрого поиска
const analyticsMap = new Map() // Map nmId to its analytics data
analyticsResults.forEach(result => {
analyticsMap.set(result.nmId, result.data)
})
cards.forEach(card => {
const stock: WBStock = {
nmId: card.nmID,
vendorCode: String(card.vendorCode || card.supplierVendorCode || ''),
title: String(card.title || card.object || `Товар ${card.nmID}`),
brand: String(card.brand || ''),
price: 0,
stocks: [],
totalQuantity: 0,
totalReserved: 0,
photos: Array.isArray(card.photos) ? card.photos : [],
mediaFiles: Array.isArray(card.mediaFiles) ? card.mediaFiles : [],
characteristics: Array.isArray(card.characteristics) ? card.characteristics : [],
subjectName: String(card.subjectName || card.object || ''),
description: String(card.description || '')
}
if (card.sizes && card.sizes.length > 0) {
stock.price = Number(card.sizes[0].price || card.sizes[0].discountedPrice) || 0
}
const analyticsData = analyticsMap.get(card.nmID)
if (analyticsData?.data?.regions) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
analyticsData.data.regions.forEach((region: any) => {
if (region.offices && region.offices.length > 0) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
region.offices.forEach((office: any) => {
stock.stocks.push({
warehouseId: office.officeID,
warehouseName: office.officeName,
quantity: office.metrics?.stockCount || 0,
quantityFull: office.metrics?.stockCount || 0,
inWayToClient: office.metrics?.toClientCount || 0,
inWayFromClient: office.metrics?.fromClientCount || 0
})
stock.totalQuantity += office.metrics?.stockCount || 0
stock.totalReserved += office.metrics?.toClientCount || 0
})
}
})
}
stocksMap.set(card.nmID, stock)
})
return Array.from(stocksMap.values()).sort((a, b) => b.totalQuantity - a.totalQuantity)
}
// Извлечение информации о складах из данных
const extractWarehousesFromStocks = (stocksData: WBStock[]): WBWarehouse[] => {
const warehousesMap = new Map<number, WBWarehouse>()
stocksData.forEach(stock => {
stock.stocks.forEach(stockInfo => {
if (!warehousesMap.has(stockInfo.warehouseId)) {
warehousesMap.set(stockInfo.warehouseId, {
id: stockInfo.warehouseId,
name: stockInfo.warehouseName,
cargoType: 1,
deliveryType: 1
})
}
})
})
return Array.from(warehousesMap.values())
}
// Обновление статистики
const updateStatistics = (stocksData: WBStock[], warehousesData: WBWarehouse[]) => {
setTotalProducts(stocksData.length)
setTotalStocks(stocksData.reduce((sum, item) => sum + item.totalQuantity, 0))
setTotalReserved(stocksData.reduce((sum, item) => sum + item.totalReserved, 0))
const totalFromClientCount = stocksData.reduce((sum, item) =>
sum + item.stocks.reduce((stockSum, stock) => stockSum + stock.inWayFromClient, 0), 0
)
setTotalFromClient(totalFromClientCount)
const warehousesWithStock = new Set(
stocksData.flatMap(item => item.stocks.map(s => s.warehouseId))
)
setActiveWarehouses(warehousesWithStock.size)
}
// Загрузка данных склада
const loadWarehouseData = async () => {
if (!user?.wildberriesApiKey) return
setLoading(true)
try {
const apiToken = user.wildberriesApiKey
const wbService = new WildberriesService(apiToken)
// 1. Получаем карточки товаров
const cards = await WildberriesService.getAllCards(apiToken).catch(() => [])
console.log('WB Warehouse: Loaded cards:', cards.length)
if (cards.length === 0) {
toast.error('Нет карточек товаров в WB')
return
}
const nmIds = cards.map(card => card.nmID).filter(id => id > 0)
console.log('WB Warehouse: NM IDs to process:', nmIds.length)
// 2. Получаем аналитику для каждого товара индивидуально
const analyticsResults = []
for (const nmId of nmIds) {
try {
console.log(`WB Warehouse: Fetching analytics for nmId ${nmId}`)
const result = await wbService.getStocksReportByOffices({
nmIDs: [nmId],
stockType: ''
})
analyticsResults.push({ nmId, data: result })
await new Promise(resolve => setTimeout(resolve, 1000))
} catch (error) {
console.error(`WB Warehouse: Error fetching analytics for nmId ${nmId}:`, error)
}
}
console.log('WB Warehouse: Analytics results:', analyticsResults.length)
// 3. Комбинируем данные
const combinedStocks = combineCardsWithIndividualAnalytics(cards, analyticsResults)
console.log('WB Warehouse: Combined stocks:', combinedStocks.length)
// 4. Извлекаем склады и обновляем статистику
const extractedWarehouses = extractWarehousesFromStocks(combinedStocks)
setStocks(combinedStocks)
setWarehouses(extractedWarehouses)
updateStatistics(combinedStocks, extractedWarehouses)
toast.success(`Загружено товаров: ${combinedStocks.length}`)
} catch (error: any) {
console.error('WB Warehouse: Error loading data:', error)
toast.error('Ошибка загрузки данных: ' + (error.message || 'Неизвестная ошибка'))
} finally {
setLoading(false)
}
}
useEffect(() => {
if (hasWBApiKey) {
loadWarehouseData()
}
}, [hasWBApiKey])
// Фильтрация товаров
const filteredStocks = stocks.filter(item => {
if (!searchTerm) return true
const search = searchTerm.toLowerCase()
return (
item.title.toLowerCase().includes(search) ||
String(item.nmId).includes(search) ||
item.brand.toLowerCase().includes(search) ||
item.vendorCode.toLowerCase().includes(search)
)
})
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">
{/* Результирующие вкладки */}
<StatsCards
totalProducts={totalProducts}
totalStocks={totalStocks}
totalReserved={totalReserved}
totalFromClient={totalFromClient}
activeWarehouses={activeWarehouses}
loading={loading}
/>
{/* Аналитика по складам WB */}
{analyticsData.length > 0 && (
<Card className="glass-card border-white/10 p-4 mb-6">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center">
<TrendingUp className="h-5 w-5 mr-2 text-blue-400" />
Движение товаров по складам WB
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{analyticsData.map((warehouse) => (
<Card key={warehouse.warehouseId} className="bg-white/5 border-white/10 p-3">
<div className="text-sm font-medium text-white mb-2">{warehouse.warehouseName}</div>
<div className="space-y-2">
<div className="flex justify-between text-xs">
<span className="text-white/60">К клиенту:</span>
<span className="text-green-400 font-medium">{warehouse.toClient}</span>
</div>
<div className="flex justify-between text-xs">
<span className="text-white/60">От клиента:</span>
<span className="text-orange-400 font-medium">{warehouse.fromClient}</span>
</div>
</div>
</Card>
))}
</div>
</Card>
)}
{/* Поиск */}
<SearchBar
searchTerm={searchTerm}
onSearchChange={setSearchTerm}
/>
{/* Список товаров */}
<div className="flex-1 overflow-hidden">
{loading ? (
<div className="overflow-y-auto pr-2 max-h-full">
<TableHeader />
<LoadingSkeleton />
</div>
) : !hasWBApiKey ? (
<Card className="glass-card border-white/10 p-8 text-center">
<Package className="h-12 w-12 text-blue-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-white mb-2">Настройте API Wildberries</h3>
<p className="text-white/60 mb-4">Для просмотра остатков добавьте API ключ Wildberries в настройках</p>
<Button
onClick={() => window.location.href = '/settings'}
className="bg-blue-600 hover:bg-blue-700"
>
Перейти в настройки
</Button>
</Card>
) : filteredStocks.length === 0 ? (
<Card className="glass-card border-white/10 p-8 text-center">
<Package className="h-12 w-12 text-white/40 mx-auto mb-4" />
<h3 className="text-lg font-medium text-white mb-2">Товары не найдены</h3>
<p className="text-white/60">Попробуйте изменить параметры поиска</p>
</Card>
) : (
<div className="overflow-y-auto pr-2 max-h-full">
<TableHeader />
{/* Таблица товаров */}
<div className="space-y-1">
{filteredStocks.map((item, index) => (
<StockTableRow key={`${item.nmId}-${index}`} item={item} />
))}
</div>
</div>
)}
</div>
</div>
</main>
</div>
)
}