Files
play-life/play-life-web/src/components/Tracking.jsx

194 lines
7.2 KiB
React
Raw Normal View History

import React, { useState, useEffect, useRef } from 'react'
import { useAuth } from './auth/AuthContext'
import './Tracking.css'
// Функция для вычисления номера недели ISO
function getISOWeek(date) {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
const dayNum = d.getUTCDay() || 7
d.setUTCDate(d.getUTCDate() + 4 - dayNum)
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
return Math.ceil((((d - yearStart) / 86400000) + 1) / 7)
}
// Функция для вычисления 5 недель (текущая + 4 предыдущие)
function getLastFiveWeeks() {
const weeks = []
const now = new Date()
for (let i = 0; i < 5; i++) {
const date = new Date(now)
date.setDate(date.getDate() - i * 7)
const week = getISOWeek(date)
const year = date.getFullYear()
weeks.push({
year,
week,
isCurrent: i === 0
})
}
return weeks.reverse() // От старой к новой
}
function Tracking({ onNavigate, activeTab }) {
const { authFetch } = useAuth()
const [weeks, setWeeks] = useState(() => getLastFiveWeeks())
const [selectedWeek, setSelectedWeek] = useState(() => {
const initialWeeks = getLastFiveWeeks()
return initialWeeks[initialWeeks.length - 1] // Текущая неделя
})
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const scrollContainerRef = useRef(null)
const currentWeekChipRef = useRef(null)
const prevActiveTabRef = useRef(null)
// Обновление списка недель и сброс выбранной недели на текущую при открытии экрана
useEffect(() => {
// Проверяем, что экран только что открылся (activeTab стал 'tracking')
if (activeTab === 'tracking' && prevActiveTabRef.current !== 'tracking') {
// Пересчитываем недели для получения актуального списка
const updatedWeeks = getLastFiveWeeks()
setWeeks(updatedWeeks)
// Устанавливаем текущую неделю (последняя в списке)
const currentWeek = updatedWeeks[updatedWeeks.length - 1]
setSelectedWeek(currentWeek)
}
prevActiveTabRef.current = activeTab
}, [activeTab])
// Скролл к чипсу текущей недели при открытии экрана
useEffect(() => {
// Выполняем скролл только когда экран открыт и только что открылся
if (activeTab === 'tracking' && currentWeekChipRef.current && scrollContainerRef.current) {
const chip = currentWeekChipRef.current
const container = scrollContainerRef.current
// Небольшая задержка для гарантии рендеринга
setTimeout(() => {
const chipLeft = chip.offsetLeft
const chipWidth = chip.offsetWidth
const containerWidth = container.offsetWidth
const scrollLeft = chipLeft - (containerWidth / 2) + (chipWidth / 2)
container.scrollTo({
left: scrollLeft,
behavior: 'smooth'
})
}, 100)
}
}, [activeTab])
// Функция для обновления данных
const refreshData = async () => {
setLoading(true)
setError(null)
try {
const res = await authFetch(`/api/tracking/stats?year=${selectedWeek.year}&week=${selectedWeek.week}`)
if (res.ok) {
setData(await res.json())
} else {
setError('Ошибка загрузки')
}
} catch (err) {
setError('Ошибка загрузки')
} finally {
setLoading(false)
}
}
// Загрузка данных при смене недели
useEffect(() => {
refreshData()
}, [selectedWeek, authFetch])
return (
<div className="tracking-screen max-w-2xl mx-auto">
{/* Заголовок с крестиком */}
<div className="tracking-header">
<h2 className="text-2xl font-semibold text-gray-800">Отслеживание</h2>
</div>
<button className="close-x-button" onClick={() => window.history.back()}></button>
{/* Чипсы недель с кнопкой доступов */}
<div className="week-controls">
<div className="week-chips-scroll" ref={scrollContainerRef}>
{weeks.map(w => (
<button
key={`${w.year}-${w.week}`}
ref={w.isCurrent ? currentWeekChipRef : null}
onClick={() => setSelectedWeek(w)}
className={`week-chip
${selectedWeek.year === w.year && selectedWeek.week === w.week ? 'selected' : ''}
${w.isCurrent ? 'current' : ''}`}
>
Неделя {w.week}
</button>
))}
</div>
<button
className="access-icon-btn"
onClick={() => onNavigate('tracking-access')}
title="Управление доступами"
>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
</div>
{/* Контент */}
{loading ? (
<div className="loading-spinner">Загрузка...</div>
) : error ? (
<div className="error-message">{error}</div>
) : (
<div className="users-list">
{data?.users.map(user => (
<UserTrackingCard key={user.user_id} user={user} />
))}
</div>
)}
</div>
)
}
// Карточка пользователя с прогрессом
function UserTrackingCard({ user }) {
// Сортируем проекты по priority (1, 2, остальные)
const sortedProjects = [...user.projects].sort((a, b) => {
const pa = a.priority ?? 99
const pb = b.priority ?? 99
return pa - pb
})
const totalPercent = user.total || 0
const getPercentColorClass = (percent) => {
return percent >= 100 ? 'percent-green' : 'percent-blue'
}
return (
<div className={`user-tracking-card ${user.is_current_user ? 'current-user' : ''}`}>
<div className="user-header">
<span className="user-name">{user.user_name}</span>
<span className={`user-total ${getPercentColorClass(totalPercent)}`}>{totalPercent.toFixed(0)}%</span>
</div>
<div className="projects-list">
{sortedProjects.map((project, idx) => {
const projectPercent = project.calculated_score || 0
return (
<div key={idx} className="project-row">
<span className="project-name">{project.project_name}</span>
<span className={`project-score ${getPercentColorClass(projectPercent)}`}>{projectPercent.toFixed(0)}%</span>
</div>
)
})}
</div>
</div>
)
}
export default Tracking