fixing
This commit is contained in:
@ -95,6 +95,11 @@ interface CampaignStatsProps {
|
||||
useCustomDates: boolean
|
||||
startDate: string
|
||||
endDate: string
|
||||
// Новые пропсы для работы с кэшем
|
||||
getCachedData?: () => any
|
||||
setCachedData?: (data: any) => void
|
||||
isLoadingData?: boolean
|
||||
setIsLoadingData?: (loading: boolean) => void
|
||||
}
|
||||
|
||||
// Интерфейсы для API данных
|
||||
@ -455,7 +460,16 @@ const CompactCampaignSelector = ({
|
||||
)
|
||||
}
|
||||
|
||||
export function AdvertisingTab({ selectedPeriod, useCustomDates, startDate, endDate }: CampaignStatsProps) {
|
||||
export function AdvertisingTab({
|
||||
selectedPeriod,
|
||||
useCustomDates,
|
||||
startDate,
|
||||
endDate,
|
||||
getCachedData,
|
||||
setCachedData,
|
||||
isLoadingData,
|
||||
setIsLoadingData
|
||||
}: CampaignStatsProps) {
|
||||
const { user } = useAuth()
|
||||
|
||||
// Состояния для раскрытия строк
|
||||
@ -481,6 +495,19 @@ export function AdvertisingTab({ selectedPeriod, useCustomDates, startDate, endD
|
||||
const [generatedLinksData, setGeneratedLinksData] = useState<Record<string, GeneratedLink[]>>({})
|
||||
const prevCampaignStats = useRef<CampaignStats[]>([])
|
||||
|
||||
// Проверяем кэш при изменении периода
|
||||
useEffect(() => {
|
||||
if (getCachedData) {
|
||||
const cachedData = getCachedData()
|
||||
if (cachedData) {
|
||||
setDailyData(cachedData.dailyData || [])
|
||||
setCampaignStats(cachedData.campaignStats || [])
|
||||
console.log('Advertising: Using cached data')
|
||||
return
|
||||
}
|
||||
}
|
||||
}, [selectedPeriod, useCustomDates, startDate, endDate, getCachedData])
|
||||
|
||||
// Вычисляем диапазон дат для запроса внешней рекламы
|
||||
const getDateRange = () => {
|
||||
if (useCustomDates && startDate && endDate) {
|
||||
@ -949,9 +976,24 @@ export function AdvertisingTab({ selectedPeriod, useCustomDates, startDate, endD
|
||||
const newDailyData = convertCampaignDataToDailyData(campaignStats)
|
||||
setDailyData(newDailyData)
|
||||
prevCampaignStats.current = campaignStats
|
||||
|
||||
// Сохраняем данные в кэш
|
||||
if (setCachedData) {
|
||||
const cacheData = {
|
||||
dailyData: newDailyData,
|
||||
campaignStats: campaignStats,
|
||||
totalCost: newDailyData.reduce((sum, day) => sum + day.totalSum, 0),
|
||||
totalViews: newDailyData.reduce((sum, day) =>
|
||||
sum + day.products.reduce((daySum, product) => daySum + product.totalViews, 0), 0),
|
||||
totalClicks: newDailyData.reduce((sum, day) =>
|
||||
sum + day.products.reduce((daySum, product) => daySum + product.totalClicks, 0), 0),
|
||||
}
|
||||
setCachedData(cacheData)
|
||||
console.log('Advertising: Data cached successfully')
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [campaignStats, externalAdsData]) // Добавляем externalAdsData в зависимости
|
||||
}, [campaignStats, externalAdsData, setCachedData]) // Добавляем externalAdsData и setCachedData в зависимости
|
||||
|
||||
const handleCampaignsSelected = (ids: number[]) => {
|
||||
if (ids.length === 0) return
|
||||
|
@ -58,6 +58,11 @@ interface SalesTabProps {
|
||||
endDate?: string
|
||||
onPeriodChange?: (period: string) => void
|
||||
onUseCustomDatesChange?: (useCustom: boolean) => void
|
||||
// Новые пропсы для работы с кэшем
|
||||
getCachedData?: () => any
|
||||
setCachedData?: (data: any) => void
|
||||
isLoadingData?: boolean
|
||||
setIsLoadingData?: (loading: boolean) => void
|
||||
}
|
||||
|
||||
// Mock данные для графиков
|
||||
@ -169,7 +174,18 @@ const mockTableData = [
|
||||
},
|
||||
]
|
||||
|
||||
export function SalesTab({ selectedPeriod, useCustomDates, startDate, endDate, onPeriodChange, onUseCustomDatesChange }: SalesTabProps) {
|
||||
export function SalesTab({
|
||||
selectedPeriod,
|
||||
useCustomDates,
|
||||
startDate,
|
||||
endDate,
|
||||
onPeriodChange,
|
||||
onUseCustomDatesChange,
|
||||
getCachedData,
|
||||
setCachedData,
|
||||
isLoadingData,
|
||||
setIsLoadingData
|
||||
}: SalesTabProps) {
|
||||
// Состояния для чекбоксов фильтрации
|
||||
const [visibleMetrics, setVisibleMetrics] = useState({
|
||||
sales: true,
|
||||
@ -179,18 +195,55 @@ export function SalesTab({ selectedPeriod, useCustomDates, startDate, endDate, o
|
||||
returns: true,
|
||||
})
|
||||
|
||||
// Получаем данные из WB API
|
||||
const { data: wbData, loading, error } = useQuery(GET_WILDBERRIES_STATISTICS, {
|
||||
// Данные для графика и таблицы
|
||||
const [chartData, setChartData] = useState<typeof mockChartData>([])
|
||||
const [tableData, setTableData] = useState<typeof mockTableData>([])
|
||||
|
||||
// Получаем данные из WB API только если нет в кэше
|
||||
const { data: wbData, loading, error, refetch } = useQuery(GET_WILDBERRIES_STATISTICS, {
|
||||
variables: useCustomDates
|
||||
? { startDate, endDate }
|
||||
: { period: selectedPeriod },
|
||||
errorPolicy: 'all',
|
||||
skip: useCustomDates && (!startDate || !endDate) // Не запрашиваем пока не выбраны обе даты
|
||||
skip: true, // Изначально пропускаем запрос, будем запускать вручную
|
||||
})
|
||||
|
||||
// Данные для графика и таблицы
|
||||
const [chartData, setChartData] = useState<typeof mockChartData>([])
|
||||
const [tableData, setTableData] = useState<typeof mockTableData>([])
|
||||
// Эффект для проверки кэша и загрузки данных
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
// Сначала проверяем локальный кэш
|
||||
if (getCachedData) {
|
||||
const cachedData = getCachedData()
|
||||
if (cachedData) {
|
||||
setChartData(cachedData.chartData || mockChartData)
|
||||
setTableData(cachedData.tableData || mockTableData)
|
||||
console.log('Sales: Using cached data')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Если нет кэша, запрашиваем данные
|
||||
if (setIsLoadingData) setIsLoadingData(true)
|
||||
|
||||
try {
|
||||
const result = await refetch()
|
||||
if (result.data?.getWildberriesStatistics?.success) {
|
||||
console.log('Sales: Loading fresh data from API')
|
||||
// Обрабатываем данные в существующем useEffect
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Sales: Error loading data:', error)
|
||||
} finally {
|
||||
if (setIsLoadingData) setIsLoadingData(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Загружаем данные только если не пропускаем загрузку
|
||||
const shouldSkip = useCustomDates && (!startDate || !endDate)
|
||||
if (!shouldSkip) {
|
||||
loadData()
|
||||
}
|
||||
}, [selectedPeriod, useCustomDates, startDate, endDate, getCachedData, refetch, setIsLoadingData])
|
||||
|
||||
useEffect(() => {
|
||||
if (wbData?.getWildberriesStatistics?.success && wbData.getWildberriesStatistics.data) {
|
||||
@ -311,8 +364,21 @@ export function SalesTab({ selectedPeriod, useCustomDates, startDate, endDate, o
|
||||
|
||||
setChartData(newChartData.reverse()) // Для графика - старые даты слева
|
||||
setTableData(newTableData) // Для таблицы - новые даты сверху
|
||||
|
||||
// Сохраняем данные в кэш
|
||||
if (setCachedData) {
|
||||
const cacheData = {
|
||||
chartData: newChartData,
|
||||
tableData: newTableData,
|
||||
totalSales: newTableData.reduce((sum, item) => sum + item.sales, 0),
|
||||
totalOrders: newTableData.reduce((sum, item) => sum + item.orders, 0),
|
||||
productsCount: newTableData.length,
|
||||
}
|
||||
setCachedData(cacheData)
|
||||
console.log('Sales: Data cached successfully')
|
||||
}
|
||||
}
|
||||
}, [wbData])
|
||||
}, [wbData, setCachedData])
|
||||
|
||||
// Функция для переключения видимости метрики
|
||||
const toggleMetric = (metric: keyof typeof visibleMetrics) => {
|
||||
@ -323,7 +389,7 @@ export function SalesTab({ selectedPeriod, useCustomDates, startDate, endDate, o
|
||||
}
|
||||
|
||||
// Проверяем состояние загрузки и данных
|
||||
const isLoading = loading || (useCustomDates && (!startDate || !endDate))
|
||||
const isLoading = (isLoadingData !== undefined ? isLoadingData : loading) || (useCustomDates && (!startDate || !endDate))
|
||||
const hasData = tableData.length > 0
|
||||
|
||||
// Состояние для сортировки
|
||||
|
@ -1,21 +1,127 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useQuery, useMutation } from '@apollo/client'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Sidebar } from '@/components/dashboard/sidebar'
|
||||
import { useSidebar } from '@/hooks/useSidebar'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import { SalesTab } from '@/components/seller-statistics/sales-tab'
|
||||
import { AdvertisingTab } from '@/components/seller-statistics/advertising-tab'
|
||||
import { DateRangePicker } from '@/components/ui/date-picker'
|
||||
import { GET_SELLER_STATS_CACHE } from '@/graphql/queries'
|
||||
import { SAVE_SELLER_STATS_CACHE } from '@/graphql/mutations'
|
||||
import { BarChart3, PieChart, TrendingUp, Calendar } from 'lucide-react'
|
||||
|
||||
export function SellerStatisticsDashboard() {
|
||||
const { getSidebarMargin } = useSidebar()
|
||||
const { user } = useAuth()
|
||||
const [selectedPeriod, setSelectedPeriod] = useState('week')
|
||||
const [useCustomDates, setUseCustomDates] = useState(false)
|
||||
const [startDate, setStartDate] = useState('')
|
||||
const [endDate, setEndDate] = useState('')
|
||||
const [activeTab, setActiveTab] = useState('sales')
|
||||
|
||||
// Кэш для данных разных периодов и табов
|
||||
const [salesCache, setSalesCache] = useState<Map<string, any>>(new Map())
|
||||
const [advertisingCache, setAdvertisingCache] = useState<Map<string, any>>(new Map())
|
||||
const [isLoadingData, setIsLoadingData] = useState(false)
|
||||
|
||||
// Мутация для сохранения кэша
|
||||
const [saveCache] = useMutation(SAVE_SELLER_STATS_CACHE)
|
||||
|
||||
// Создаём ключ для кэша на основе периода и дат
|
||||
const getCacheKey = () => {
|
||||
if (useCustomDates && startDate && endDate) {
|
||||
return `custom_${startDate}_${endDate}`
|
||||
}
|
||||
return selectedPeriod
|
||||
}
|
||||
|
||||
// Проверяем есть ли данные в локальном кэше
|
||||
const getCachedData = (type: 'sales' | 'advertising') => {
|
||||
const cache = type === 'sales' ? salesCache : advertisingCache
|
||||
const cacheKey = getCacheKey()
|
||||
return cache.get(cacheKey)
|
||||
}
|
||||
|
||||
// Сохраняем данные в локальный кэш
|
||||
const setCachedData = (type: 'sales' | 'advertising', data: any) => {
|
||||
const cacheKey = getCacheKey()
|
||||
if (type === 'sales') {
|
||||
setSalesCache(new Map(salesCache.set(cacheKey, data)))
|
||||
} else {
|
||||
setAdvertisingCache(new Map(advertisingCache.set(cacheKey, data)))
|
||||
}
|
||||
}
|
||||
|
||||
// Запрос кэша из БД
|
||||
const { data: cacheData, refetch: refetchCache } = useQuery(GET_SELLER_STATS_CACHE, {
|
||||
variables: {
|
||||
period: useCustomDates ? 'custom' : selectedPeriod,
|
||||
dateFrom: useCustomDates ? startDate : undefined,
|
||||
dateTo: useCustomDates ? endDate : undefined,
|
||||
},
|
||||
skip: !user?.organization,
|
||||
fetchPolicy: 'cache-first',
|
||||
errorPolicy: 'ignore',
|
||||
})
|
||||
|
||||
// Загружаем данные из кэша БД при изменении периода
|
||||
useEffect(() => {
|
||||
if (cacheData?.getSellerStatsCache?.success && cacheData.getSellerStatsCache.cache) {
|
||||
const cache = cacheData.getSellerStatsCache.cache
|
||||
const cacheKey = getCacheKey()
|
||||
|
||||
// Проверяем не истёк ли кэш (24 часа)
|
||||
const expiresAt = new Date(cache.expiresAt)
|
||||
const now = new Date()
|
||||
|
||||
if (expiresAt > now) {
|
||||
// Кэш актуален, загружаем данные
|
||||
if (cache.productsData) {
|
||||
setSalesCache(new Map(salesCache.set(cacheKey, JSON.parse(cache.productsData))))
|
||||
}
|
||||
if (cache.advertisingData) {
|
||||
setAdvertisingCache(new Map(advertisingCache.set(cacheKey, JSON.parse(cache.advertisingData))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [cacheData, selectedPeriod, useCustomDates, startDate, endDate])
|
||||
|
||||
// Сохраняем данные в БД кэш
|
||||
const saveToCacheDB = async (type: 'sales' | 'advertising', data: any) => {
|
||||
try {
|
||||
const cacheKey = getCacheKey()
|
||||
const expiresAt = new Date()
|
||||
expiresAt.setHours(expiresAt.getHours() + 24) // 24 часа
|
||||
|
||||
const input: any = {
|
||||
period: useCustomDates ? 'custom' : selectedPeriod,
|
||||
dateFrom: useCustomDates ? startDate : null,
|
||||
dateTo: useCustomDates ? endDate : null,
|
||||
expiresAt: expiresAt.toISOString(),
|
||||
}
|
||||
|
||||
if (type === 'sales') {
|
||||
input.productsData = JSON.stringify(data)
|
||||
input.productsTotalSales = data.totalSales || 0
|
||||
input.productsTotalOrders = data.totalOrders || 0
|
||||
input.productsCount = data.productsCount || 0
|
||||
} else {
|
||||
input.advertisingData = JSON.stringify(data)
|
||||
input.advertisingTotalCost = data.totalCost || 0
|
||||
input.advertisingTotalViews = data.totalViews || 0
|
||||
input.advertisingTotalClicks = data.totalClicks || 0
|
||||
}
|
||||
|
||||
await saveCache({ variables: { input } })
|
||||
console.log(`Cached ${type} data saved to DB for period ${cacheKey}`)
|
||||
} catch (error) {
|
||||
console.error(`Error saving ${type} cache:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen flex overflow-hidden">
|
||||
@ -25,7 +131,7 @@ export function SellerStatisticsDashboard() {
|
||||
|
||||
{/* Основной контент с табами */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<Tabs defaultValue="sales" className="h-full flex flex-col">
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab} className="h-full flex flex-col">
|
||||
<TabsList className="grid w-full grid-cols-3 bg-white/5 backdrop-blur border border-white/10 rounded-xl flex-shrink-0 h-11">
|
||||
<TabsTrigger
|
||||
value="sales"
|
||||
@ -60,6 +166,14 @@ export function SellerStatisticsDashboard() {
|
||||
endDate={endDate}
|
||||
onPeriodChange={setSelectedPeriod}
|
||||
onUseCustomDatesChange={setUseCustomDates}
|
||||
// Передаём функции для работы с кэшем
|
||||
getCachedData={() => getCachedData('sales')}
|
||||
setCachedData={(data) => {
|
||||
setCachedData('sales', data)
|
||||
saveToCacheDB('sales', data)
|
||||
}}
|
||||
isLoadingData={isLoadingData}
|
||||
setIsLoadingData={setIsLoadingData}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
@ -69,6 +183,14 @@ export function SellerStatisticsDashboard() {
|
||||
useCustomDates={useCustomDates}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
// Передаём функции для работы с кэшем
|
||||
getCachedData={() => getCachedData('advertising')}
|
||||
setCachedData={(data) => {
|
||||
setCachedData('advertising', data)
|
||||
saveToCacheDB('advertising', data)
|
||||
}}
|
||||
isLoadingData={isLoadingData}
|
||||
setIsLoadingData={setIsLoadingData}
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
|
@ -1334,3 +1334,32 @@ export const SAVE_WB_WAREHOUSE_CACHE = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Мутации для кеша статистики продаж
|
||||
export const SAVE_SELLER_STATS_CACHE = gql`
|
||||
mutation SaveSellerStatsCache($input: SellerStatsCacheInput!) {
|
||||
saveSellerStatsCache(input: $input) {
|
||||
success
|
||||
message
|
||||
cache {
|
||||
id
|
||||
organizationId
|
||||
cacheDate
|
||||
period
|
||||
dateFrom
|
||||
dateTo
|
||||
productsData
|
||||
productsTotalSales
|
||||
productsTotalOrders
|
||||
productsCount
|
||||
advertisingData
|
||||
advertisingTotalCost
|
||||
advertisingTotalViews
|
||||
advertisingTotalClicks
|
||||
expiresAt
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -977,3 +977,41 @@ export const GET_WB_WAREHOUSE_DATA = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Запросы для кеша статистики продаж
|
||||
export const GET_SELLER_STATS_CACHE = gql`
|
||||
query GetSellerStatsCache(
|
||||
$period: String!
|
||||
$dateFrom: String
|
||||
$dateTo: String
|
||||
) {
|
||||
getSellerStatsCache(
|
||||
period: $period
|
||||
dateFrom: $dateFrom
|
||||
dateTo: $dateTo
|
||||
) {
|
||||
success
|
||||
message
|
||||
fromCache
|
||||
cache {
|
||||
id
|
||||
organizationId
|
||||
cacheDate
|
||||
period
|
||||
dateFrom
|
||||
dateTo
|
||||
productsData
|
||||
productsTotalSales
|
||||
productsTotalOrders
|
||||
productsCount
|
||||
advertisingData
|
||||
advertisingTotalCost
|
||||
advertisingTotalViews
|
||||
advertisingTotalClicks
|
||||
expiresAt
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
Reference in New Issue
Block a user