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