202
play-life-web/src/components/FullStatistics.jsx
Normal file
202
play-life-web/src/components/FullStatistics.jsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react'
|
||||
import WeekProgressChart from './WeekProgressChart'
|
||||
import LoadingError from './LoadingError'
|
||||
import TodayEntriesList from './TodayEntriesList'
|
||||
import { getAllProjectsSorted } from '../utils/projectUtils'
|
||||
import './Integrations.css'
|
||||
|
||||
// Экспортируем для обратной совместимости (если используется в других местах)
|
||||
export { getProjectColorByIndex } from '../utils/projectUtils'
|
||||
|
||||
// Функция для получения дат текущей недели (понедельник - воскресенье)
|
||||
const getCurrentWeekDates = () => {
|
||||
const now = new Date()
|
||||
const day = now.getDay()
|
||||
// Вычисляем разницу до понедельника (1 = понедельник, 0 = воскресенье)
|
||||
const diff = day === 0 ? -6 : 1 - day
|
||||
const monday = new Date(now)
|
||||
monday.setDate(now.getDate() + diff)
|
||||
|
||||
const dates = []
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(monday)
|
||||
date.setDate(monday.getDate() + i)
|
||||
dates.push(date)
|
||||
}
|
||||
return dates
|
||||
}
|
||||
|
||||
// Функция для форматирования даты в YYYY-MM-DD
|
||||
const formatDate = (date) => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
// Названия дней недели
|
||||
const dayNames = ['пн', 'вт', 'ср', 'чт', 'пт', 'сб', 'вс']
|
||||
|
||||
function FullStatistics({ selectedProject, onClearSelection, data, loading, error, onRetry, currentWeekData, onNavigate, todayEntries, todayEntriesLoading, todayEntriesError, onRetryTodayEntries, fetchTodayEntries, activeTab }) {
|
||||
const [selectedDate, setSelectedDate] = useState(null)
|
||||
const prevActiveTabRef = React.useRef(activeTab)
|
||||
const componentJustOpenedRef = React.useRef(false)
|
||||
|
||||
// Получаем даты текущей недели
|
||||
const weekDates = getCurrentWeekDates()
|
||||
|
||||
// Определяем текущий день (используем useMemo для стабильности)
|
||||
const today = React.useMemo(() => {
|
||||
const date = new Date()
|
||||
date.setHours(0, 0, 0, 0)
|
||||
return date
|
||||
}, [])
|
||||
|
||||
// Получаем строковое представление сегодняшней даты
|
||||
const todayDateStr = React.useMemo(() => formatDate(today), [today])
|
||||
|
||||
// Фильтруем только прошедшие дни (включая сегодня)
|
||||
const pastDays = weekDates.filter((date) => {
|
||||
const dateOnly = new Date(date)
|
||||
dateOnly.setHours(0, 0, 0, 0)
|
||||
return dateOnly <= today
|
||||
})
|
||||
|
||||
// Отслеживаем выход с экрана и сбрасываем выбор дня
|
||||
useEffect(() => {
|
||||
// Если мы были на экране full и перешли на другой экран - сбрасываем выбор дня
|
||||
if (prevActiveTabRef.current === 'full' && activeTab !== 'full') {
|
||||
setSelectedDate(todayDateStr)
|
||||
}
|
||||
}, [activeTab, todayDateStr])
|
||||
|
||||
// Инициализируем выбранную дату текущим днем при первом рендере
|
||||
// Также проверяем, что выбранная дата все еще в списке доступных дней
|
||||
useEffect(() => {
|
||||
const pastDaysDateStrs = pastDays.map(date => formatDate(date))
|
||||
|
||||
if (selectedDate === null) {
|
||||
// Первая инициализация - устанавливаем текущий день
|
||||
setSelectedDate(todayDateStr)
|
||||
} else if (!pastDaysDateStrs.includes(selectedDate)) {
|
||||
// Если выбранная дата больше не в списке доступных (например, прошла неделя)
|
||||
// Сбрасываем на текущий день
|
||||
setSelectedDate(todayDateStr)
|
||||
}
|
||||
}, [selectedDate, todayDateStr, pastDays])
|
||||
|
||||
// Отслеживаем открытие компонента
|
||||
useEffect(() => {
|
||||
// Когда компонент открывается (activeTab становится 'full'), помечаем это
|
||||
if (activeTab === 'full' && prevActiveTabRef.current !== 'full') {
|
||||
componentJustOpenedRef.current = true
|
||||
}
|
||||
prevActiveTabRef.current = activeTab
|
||||
}, [activeTab])
|
||||
|
||||
// Загружаем данные при изменении selectedDate или selectedProject
|
||||
useEffect(() => {
|
||||
if (selectedDate && fetchTodayEntries) {
|
||||
// Если компонент только что открылся - используем фоновую загрузку
|
||||
if (componentJustOpenedRef.current) {
|
||||
componentJustOpenedRef.current = false
|
||||
fetchTodayEntries(true, selectedProject, selectedDate)
|
||||
} else {
|
||||
// При изменении даты или проекта - используем обычную загрузку (не фоновую)
|
||||
fetchTodayEntries(false, selectedProject, selectedDate)
|
||||
}
|
||||
}
|
||||
}, [selectedDate, selectedProject, fetchTodayEntries])
|
||||
|
||||
// Обработчик выбора дня
|
||||
const handleDaySelect = useCallback((date) => {
|
||||
const dateStr = formatDate(date)
|
||||
setSelectedDate(dateStr)
|
||||
// Загрузка данных произойдет автоматически через useEffect выше
|
||||
}, [])
|
||||
|
||||
if (error && (!data || data.length === 0) && !loading) {
|
||||
return <LoadingError onRetry={onRetry} />
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{onNavigate && (
|
||||
<button
|
||||
onClick={() => {
|
||||
// Сбрасываем выбор дня перед выходом с экрана
|
||||
setSelectedDate(todayDateStr)
|
||||
window.history.back()
|
||||
}}
|
||||
className="close-x-button"
|
||||
title="Закрыть"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
)}
|
||||
{loading && (!data || data.length === 0) && (!todayEntries || (Array.isArray(todayEntries) && todayEntries.length === 0)) ? (
|
||||
<div className="fixed inset-0 flex justify-center items-center">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="w-12 h-12 border-4 border-indigo-200 border-t-indigo-600 rounded-full animate-spin mb-4"></div>
|
||||
<div className="text-gray-600 font-medium">Загрузка...</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (!data || data.length === 0) && !todayEntries ? (
|
||||
<div className="flex justify-center items-center py-16">
|
||||
<div className="text-gray-500 text-lg">Нет данных для отображения</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<WeekProgressChart data={data} allProjectsSorted={getAllProjectsSorted(data)} currentWeekData={currentWeekData} selectedProject={selectedProject} />
|
||||
|
||||
{/* Чипсы дней недели */}
|
||||
{pastDays.length > 0 && (
|
||||
<div className="mt-3 mb-2">
|
||||
<div className="flex flex-wrap gap-2.5">
|
||||
{pastDays.map((date, index) => {
|
||||
const dateStr = formatDate(date)
|
||||
const dayOfWeek = index + 1 // 1 = понедельник
|
||||
const isSelected = selectedDate === dateStr
|
||||
const isToday = dateStr === todayDateStr
|
||||
|
||||
return (
|
||||
<button
|
||||
key={dateStr}
|
||||
onClick={() => handleDaySelect(date)}
|
||||
className={`
|
||||
h-9 px-4 rounded-lg text-sm font-semibold
|
||||
transition-all duration-200 ease-in-out
|
||||
flex items-center justify-center
|
||||
${
|
||||
isSelected
|
||||
? 'bg-white text-gray-900 shadow-sm border border-gray-200'
|
||||
: 'bg-transparent text-gray-700 border border-gray-300 hover:border-gray-400'
|
||||
}
|
||||
${isToday && !isSelected ? 'ring-2 ring-indigo-200 border-indigo-300' : ''}
|
||||
active:scale-95
|
||||
`}
|
||||
>
|
||||
{dayNames[dayOfWeek - 1]}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TodayEntriesList
|
||||
data={todayEntries}
|
||||
loading={todayEntriesLoading}
|
||||
error={todayEntriesError}
|
||||
onRetry={() => fetchTodayEntries && fetchTodayEntries(false, selectedProject, selectedDate)}
|
||||
onDelete={() => fetchTodayEntries && fetchTodayEntries(false, selectedProject, selectedDate)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FullStatistics
|
||||
|
||||
Reference in New Issue
Block a user