203 lines
8.6 KiB
JavaScript
203 lines
8.6 KiB
JavaScript
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
|
||
|