4.11.0: Редактирование экрана статистики
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m1s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m1s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "4.10.2",
|
"version": "4.11.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -550,11 +550,9 @@ function AppContent() {
|
|||||||
setActiveTab(tab)
|
setActiveTab(tab)
|
||||||
setTabParams(params)
|
setTabParams(params)
|
||||||
markTabAsLoaded(tab)
|
markTabAsLoaded(tab)
|
||||||
// Если это экран full с selectedProject, восстанавливаем его
|
// Если это экран full, устанавливаем selectedProject только если он есть в params
|
||||||
if (tab === 'full' && params.selectedProject) {
|
if (tab === 'full') {
|
||||||
setSelectedProject(params.selectedProject)
|
setSelectedProject(params.selectedProject || null)
|
||||||
} else if (tab === 'full') {
|
|
||||||
setSelectedProject(null)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -580,9 +578,9 @@ function AppContent() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
setTabParams(params)
|
setTabParams(params)
|
||||||
// Если это экран full с selectedProject, восстанавливаем его
|
// Если это экран full, устанавливаем selectedProject только если он есть в params
|
||||||
if (tabFromUrl === 'full' && params.selectedProject) {
|
if (tabFromUrl === 'full') {
|
||||||
setSelectedProject(params.selectedProject)
|
setSelectedProject(params.selectedProject || null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Если в URL нет глубокого таба, значит мы вернулись на основной таб
|
// Если в URL нет глубокого таба, значит мы вернулись на основной таб
|
||||||
@@ -697,6 +695,11 @@ function AppContent() {
|
|||||||
setActiveTab(tab)
|
setActiveTab(tab)
|
||||||
if (tab === 'current') {
|
if (tab === 'current') {
|
||||||
setSelectedProject(null)
|
setSelectedProject(null)
|
||||||
|
} else if (tab === 'full') {
|
||||||
|
// Если переходим на full без selectedProject в params, очищаем выбранный проект
|
||||||
|
if (!params.selectedProject) {
|
||||||
|
setSelectedProject(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Обновляем список слов при возврате из экрана добавления слов
|
// Обновляем список слов при возврате из экрана добавления слов
|
||||||
if (activeTab === 'add-words' && tab === 'words') {
|
if (activeTab === 'add-words' && tab === 'words') {
|
||||||
|
|||||||
@@ -1,187 +1,18 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {
|
|
||||||
Chart as ChartJS,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
Filler
|
|
||||||
} from 'chart.js'
|
|
||||||
import { Line } from 'react-chartjs-2'
|
|
||||||
import WeekProgressChart from './WeekProgressChart'
|
import WeekProgressChart from './WeekProgressChart'
|
||||||
import LoadingError from './LoadingError'
|
import LoadingError from './LoadingError'
|
||||||
import { getAllProjectsSorted, getProjectColor, sortProjectsLikeCurrentWeek } from '../utils/projectUtils'
|
import { getAllProjectsSorted } from '../utils/projectUtils'
|
||||||
import './Integrations.css'
|
import './Integrations.css'
|
||||||
|
|
||||||
// Экспортируем для обратной совместимости (если используется в других местах)
|
// Экспортируем для обратной совместимости (если используется в других местах)
|
||||||
export { getProjectColorByIndex } from '../utils/projectUtils'
|
export { getProjectColorByIndex } from '../utils/projectUtils'
|
||||||
|
|
||||||
const parseWeekKey = (weekKey) => {
|
|
||||||
const [yearStr, weekStr] = weekKey.split('-W')
|
|
||||||
return { year: Number(yearStr), week: Number(weekStr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
const compareWeekKeys = (a, b) => {
|
|
||||||
const [yearA, weekA] = a.split('-W').map(Number)
|
|
||||||
const [yearB, weekB] = b.split('-W').map(Number)
|
|
||||||
|
|
||||||
if (yearA !== yearB) {
|
|
||||||
return yearA - yearB
|
|
||||||
}
|
|
||||||
return weekA - weekB
|
|
||||||
}
|
|
||||||
|
|
||||||
ChartJS.register(
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
PointElement,
|
|
||||||
LineElement,
|
|
||||||
Title,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
Filler
|
|
||||||
)
|
|
||||||
|
|
||||||
function FullStatistics({ selectedProject, onClearSelection, data, loading, error, onRetry, currentWeekData, onNavigate }) {
|
function FullStatistics({ selectedProject, onClearSelection, data, loading, error, onRetry, currentWeekData, onNavigate }) {
|
||||||
|
|
||||||
const processData = () => {
|
if (error && (!data || data.length === 0) && !loading) {
|
||||||
if (!data || data.length === 0) return null
|
|
||||||
|
|
||||||
// Группируем данные по проектам
|
|
||||||
const projectsMap = {}
|
|
||||||
|
|
||||||
data.forEach(item => {
|
|
||||||
const projectName = item.project_name
|
|
||||||
const weekKey = `${item.report_year}-W${item.report_week.toString().padStart(2, '0')}`
|
|
||||||
|
|
||||||
if (!projectsMap[projectName]) {
|
|
||||||
projectsMap[projectName] = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
projectsMap[projectName][weekKey] = parseFloat(item.total_score)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Собираем все уникальные недели и сортируем их по году и неделе
|
|
||||||
const allWeeks = new Set()
|
|
||||||
Object.values(projectsMap).forEach(weeks => {
|
|
||||||
Object.keys(weeks).forEach(week => allWeeks.add(week))
|
|
||||||
})
|
|
||||||
const sortedWeeks = Array.from(allWeeks).sort(compareWeekKeys)
|
|
||||||
|
|
||||||
// Получаем отсортированный список всех проектов для синхронизации цветов
|
|
||||||
const allProjectNames = getAllProjectsSorted(data)
|
|
||||||
|
|
||||||
// Фильтруем по выбранному проекту, если он указан
|
|
||||||
let projectNames = allProjectNames.filter(name => projectsMap[name])
|
|
||||||
|
|
||||||
// Сортируем проекты так же, как на экране списка проектов (по priority и min_goal_score)
|
|
||||||
if (currentWeekData) {
|
|
||||||
projectNames = sortProjectsLikeCurrentWeek(projectNames, currentWeekData)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedProject) {
|
|
||||||
projectNames = projectNames.filter(name => name === selectedProject)
|
|
||||||
}
|
|
||||||
|
|
||||||
const datasets = projectNames.map((projectName) => {
|
|
||||||
let cumulativeSum = 0
|
|
||||||
const values = sortedWeeks.map(week => {
|
|
||||||
const score = projectsMap[projectName]?.[week] || 0
|
|
||||||
cumulativeSum += score
|
|
||||||
return cumulativeSum
|
|
||||||
})
|
|
||||||
|
|
||||||
// Генерируем цвет для линии на основе индекса в полном списке проектов
|
|
||||||
const color = getProjectColor(projectName, allProjectNames)
|
|
||||||
|
|
||||||
return {
|
|
||||||
label: projectName,
|
|
||||||
data: values,
|
|
||||||
borderColor: color,
|
|
||||||
backgroundColor: color.replace('50%)', '20%)'),
|
|
||||||
fill: false,
|
|
||||||
tension: 0.4,
|
|
||||||
pointRadius: 4,
|
|
||||||
pointHoverRadius: 6,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
labels: sortedWeeks,
|
|
||||||
datasets: datasets,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const chartData = processData()
|
|
||||||
|
|
||||||
if (error && !chartData && !loading) {
|
|
||||||
return <LoadingError onRetry={onRetry} />
|
return <LoadingError onRetry={onRetry} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const chartOptions = {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
animation: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
usePointStyle: true,
|
|
||||||
padding: 15,
|
|
||||||
font: {
|
|
||||||
size: 12,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
padding: {
|
|
||||||
top: 20,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
callbacks: {
|
|
||||||
label: function(context) {
|
|
||||||
return `${context.dataset.label}: ${context.parsed.y.toFixed(2)}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
display: true,
|
|
||||||
title: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
display: true,
|
|
||||||
color: 'rgba(0, 0, 0, 0.05)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
display: true,
|
|
||||||
title: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
beginAtZero: true,
|
|
||||||
grid: {
|
|
||||||
display: true,
|
|
||||||
color: 'rgba(0, 0, 0, 0.05)',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
mode: 'nearest',
|
|
||||||
axis: 'x',
|
|
||||||
intersect: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto">
|
<div className="max-w-2xl mx-auto">
|
||||||
@@ -194,24 +25,19 @@ function FullStatistics({ selectedProject, onClearSelection, data, loading, erro
|
|||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{loading && !chartData ? (
|
{loading && (!data || data.length === 0) ? (
|
||||||
<div className="fixed inset-0 flex justify-center items-center">
|
<div className="fixed inset-0 flex justify-center items-center">
|
||||||
<div className="flex flex-col 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="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 className="text-gray-600 font-medium">Загрузка...</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : !chartData ? (
|
) : (!data || data.length === 0) ? (
|
||||||
<div className="flex justify-center items-center py-16">
|
<div className="flex justify-center items-center py-16">
|
||||||
<div className="text-gray-500 text-lg">Нет данных для отображения</div>
|
<div className="text-gray-500 text-lg">Нет данных для отображения</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
|
||||||
<div style={{ height: '550px', paddingTop: '100px' }}>
|
|
||||||
<Line data={chartData} options={chartOptions} />
|
|
||||||
</div>
|
|
||||||
<WeekProgressChart data={data} allProjectsSorted={getAllProjectsSorted(data)} currentWeekData={currentWeekData} selectedProject={selectedProject} />
|
<WeekProgressChart data={data} allProjectsSorted={getAllProjectsSorted(data)} currentWeekData={currentWeekData} selectedProject={selectedProject} />
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,14 +18,55 @@ const compareWeekKeys = (a, b) => {
|
|||||||
return weekA - weekB
|
return weekA - weekB
|
||||||
}
|
}
|
||||||
|
|
||||||
function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedProject }) {
|
// Функция для определения текущей недели в ISO формате
|
||||||
if (!data || data.length === 0) {
|
// ISO 8601: неделя начинается с понедельника, первая неделя года - та, в которой есть 4 января
|
||||||
return null
|
const getCurrentWeek = () => {
|
||||||
|
const now = new Date()
|
||||||
|
const date = new Date(now.getTime())
|
||||||
|
|
||||||
|
// ISO week calculation
|
||||||
|
// Set to nearest Thursday: current date + 4 - current day number
|
||||||
|
// Make Sunday's day number 7
|
||||||
|
const day = date.getDay() || 7
|
||||||
|
date.setDate(date.getDate() + 4 - day)
|
||||||
|
|
||||||
|
// Get first day of year
|
||||||
|
const yearStart = new Date(date.getFullYear(), 0, 1)
|
||||||
|
|
||||||
|
// Calculate full weeks to nearest Thursday
|
||||||
|
const week = Math.ceil((((date - yearStart) / 86400000) + 1) / 7)
|
||||||
|
|
||||||
|
// ISO year is the year of the Thursday of that week
|
||||||
|
const year = date.getFullYear()
|
||||||
|
|
||||||
|
return { year, week }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedProject }) {
|
||||||
|
// Определяем текущую неделю
|
||||||
|
const currentWeek = getCurrentWeek()
|
||||||
|
const currentWeekKey = formatWeekKey(currentWeek)
|
||||||
|
|
||||||
|
// Используем переданный отсортированный список проектов или получаем из данных
|
||||||
|
const allProjects = allProjectsSorted || (() => {
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
// Если нет данных, используем проекты из currentWeekData
|
||||||
|
if (currentWeekData && currentWeekData.projects) {
|
||||||
|
return currentWeekData.projects.map(p => p.project_name || p.name).filter(Boolean)
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const allProjectsSet = new Set()
|
||||||
|
data.forEach(item => {
|
||||||
|
allProjectsSet.add(item.project_name)
|
||||||
|
})
|
||||||
|
return Array.from(allProjectsSet).sort()
|
||||||
|
})()
|
||||||
|
|
||||||
// Группируем данные по неделям
|
// Группируем данные по неделям
|
||||||
const weeksMap = {}
|
const weeksMap = {}
|
||||||
|
|
||||||
|
if (data && data.length > 0) {
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
// Фильтруем по выбранному проекту, если он указан
|
// Фильтруем по выбранному проекту, если он указан
|
||||||
if (selectedProject && item.project_name !== selectedProject) {
|
if (selectedProject && item.project_name !== selectedProject) {
|
||||||
@@ -43,25 +84,70 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
|
|||||||
score: parseFloat(item.total_score) || 0
|
score: parseFloat(item.total_score) || 0
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем данные текущей недели из currentWeekData, если они отсутствуют в data
|
||||||
|
if (currentWeekData && currentWeekData.projects && !weeksMap[currentWeekKey]) {
|
||||||
|
const projects = Array.isArray(currentWeekData.projects)
|
||||||
|
? currentWeekData.projects
|
||||||
|
: (currentWeekData.projects?.projects || [])
|
||||||
|
|
||||||
|
weeksMap[currentWeekKey] = projects.map(project => ({
|
||||||
|
projectName: project.project_name || project.name,
|
||||||
|
score: parseFloat(project.total_score || project.score || 0)
|
||||||
|
})).filter(p => {
|
||||||
|
// Фильтруем по выбранному проекту, если он указан
|
||||||
|
if (selectedProject && p.projectName !== selectedProject) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Получаем все уникальные недели и сортируем их (новые сверху)
|
// Получаем все уникальные недели и сортируем их (новые сверху)
|
||||||
const allWeeks = Object.keys(weeksMap).sort((a, b) => -compareWeekKeys(a, b))
|
const allWeeks = Object.keys(weeksMap).sort((a, b) => -compareWeekKeys(a, b))
|
||||||
|
|
||||||
// Берем первые 4 недели (самые актуальные)
|
// Находим индекс текущей недели
|
||||||
const last4Weeks = allWeeks.slice(0, 4)
|
const currentWeekIndex = allWeeks.findIndex(w => w === currentWeekKey)
|
||||||
|
|
||||||
// Используем переданный отсортированный список проектов или получаем из данных
|
// Разделяем недели на группы
|
||||||
const allProjects = allProjectsSorted || (() => {
|
// allWeeks отсортированы от новых к старым (индекс 0 - самая новая)
|
||||||
const allProjectsSet = new Set()
|
// Если currentWeekIndex = 2:
|
||||||
data.forEach(item => {
|
// - allWeeks[0, 1] - более новые недели (будущие, если есть)
|
||||||
allProjectsSet.add(item.project_name)
|
// - allWeeks[2] - текущая неделя
|
||||||
})
|
// - allWeeks[3, 4, ...] - более старые недели (прошлые)
|
||||||
return Array.from(allProjectsSet).sort()
|
let last4Weeks = [] // Последние 4 недели ДО текущей (более старые)
|
||||||
})()
|
let next4Weeks = [] // Следующие 4 недели ПОСЛЕ текущей (более новые, если есть)
|
||||||
|
let currentWeekInData = null
|
||||||
|
|
||||||
// Обрабатываем данные для каждой недели
|
if (currentWeekIndex >= 0) {
|
||||||
const weeksData = last4Weeks.map(weekKey => {
|
// Текущая неделя найдена в данных
|
||||||
const weekProjects = weeksMap[weekKey]
|
// Берем 4 недели ДО текущей (более старые) - это индексы после currentWeekIndex
|
||||||
|
last4Weeks = allWeeks.slice(currentWeekIndex + 1, currentWeekIndex + 5)
|
||||||
|
// Берем 4 недели ПОСЛЕ текущей (более новые) - это индексы до currentWeekIndex
|
||||||
|
next4Weeks = allWeeks.slice(Math.max(0, currentWeekIndex - 4), currentWeekIndex)
|
||||||
|
// Текущая неделя
|
||||||
|
currentWeekInData = currentWeekKey
|
||||||
|
} else {
|
||||||
|
// Текущая неделя не найдена в данных, но мы её добавили из currentWeekData
|
||||||
|
if (weeksMap[currentWeekKey]) {
|
||||||
|
// Добавляем текущую неделю в начало списка (она самая новая)
|
||||||
|
const weeksWithCurrent = [currentWeekKey, ...allWeeks]
|
||||||
|
// Последние 4 недели - это первые 4 из старых данных (более старые)
|
||||||
|
last4Weeks = allWeeks.slice(0, 4)
|
||||||
|
// Следующие недели после текущей - их нет, так как текущая самая новая
|
||||||
|
next4Weeks = []
|
||||||
|
currentWeekInData = currentWeekKey
|
||||||
|
} else {
|
||||||
|
// Если текущей недели нет вообще, просто берем последние 4 (самые новые из доступных)
|
||||||
|
last4Weeks = allWeeks.slice(0, 4)
|
||||||
|
next4Weeks = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция для обработки данных недели
|
||||||
|
const processWeekData = (weekKey) => {
|
||||||
|
const weekProjects = weeksMap[weekKey] || []
|
||||||
const totalScore = weekProjects.reduce((sum, p) => sum + p.score, 0)
|
const totalScore = weekProjects.reduce((sum, p) => sum + p.score, 0)
|
||||||
|
|
||||||
// Используем абсолютные значения (баллы)
|
// Используем абсолютные значения (баллы)
|
||||||
@@ -93,23 +179,36 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
|
|||||||
year,
|
year,
|
||||||
week,
|
week,
|
||||||
projects: sortedProjectsWithData,
|
projects: sortedProjectsWithData,
|
||||||
totalScore
|
totalScore,
|
||||||
|
isCurrent: weekKey === currentWeekKey
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
// Обрабатываем данные для всех недель
|
||||||
|
const currentWeekDataProcessed = currentWeekInData ? processWeekData(currentWeekInData) : null
|
||||||
|
|
||||||
|
// Объединяем все недели кроме текущей
|
||||||
|
// next4Weeks уже отсортированы от новых к старым (индексы 0..currentWeekIndex-1)
|
||||||
|
// last4Weeks уже отсортированы от новых к старым (индексы currentWeekIndex+1..)
|
||||||
|
// Для отображения: старые сверху, новые снизу - нужно перевернуть порядок
|
||||||
|
// Объединяем: сначала более старые (last4Weeks), потом более новые (next4Weeks)
|
||||||
|
const allOtherWeeks = [...last4Weeks, ...next4Weeks]
|
||||||
|
// Переворачиваем, чтобы старые были сверху, новые снизу
|
||||||
|
const allOtherWeeksData = allOtherWeeks.map(processWeekData).reverse()
|
||||||
|
|
||||||
|
// Объединяем все недели для расчета максимального значения
|
||||||
|
const allWeeksData = [...(currentWeekDataProcessed ? [currentWeekDataProcessed] : []), ...allOtherWeeksData]
|
||||||
|
|
||||||
// Находим максимальное значение среди всех недель для единой шкалы сравнения
|
// Находим максимальное значение среди всех недель для единой шкалы сравнения
|
||||||
const maxTotalScore = Math.max(...weeksData.map(w => w.totalScore), 1)
|
const maxTotalScore = Math.max(...allWeeksData.map(w => w.totalScore), 1)
|
||||||
|
|
||||||
if (weeksData.length === 0) {
|
if (allWeeksData.length === 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
// Компонент для отображения прогрессбара недели
|
||||||
<div className="mt-8">
|
const WeekProgressBar = ({ weekData }) => (
|
||||||
<h2 className="text-xl font-semibold text-gray-800 mb-4">Последние 4 недели</h2>
|
<div className="flex items-center gap-3">
|
||||||
<div className="space-y-3">
|
|
||||||
{weeksData.map((weekData) => (
|
|
||||||
<div key={weekData.weekKey} className="flex items-center gap-3">
|
|
||||||
<div className="min-w-[100px] text-sm font-medium text-gray-700">
|
<div className="min-w-[100px] text-sm font-medium text-gray-700">
|
||||||
Неделя {weekData.week}
|
Неделя {weekData.week}
|
||||||
</div>
|
</div>
|
||||||
@@ -150,7 +249,29 @@ function WeekProgressChart({ data, allProjectsSorted, currentWeekData, selectedP
|
|||||||
{weekData.totalScore > 0 ? `${weekData.totalScore.toFixed(1)}` : '-'}
|
{weekData.totalScore > 0 ? `${weekData.totalScore.toFixed(1)}` : '-'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mt-8">
|
||||||
|
<h2 className="text-2xl font-semibold text-gray-800 mb-6">Прогресс недель</h2>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{/* Остальные недели (старые сверху, новые снизу) */}
|
||||||
|
{allOtherWeeksData.map((weekData) => (
|
||||||
|
<WeekProgressBar key={weekData.weekKey} weekData={weekData} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Разделитель перед текущей неделей */}
|
||||||
|
{currentWeekDataProcessed && (
|
||||||
|
<div className="border-t border-gray-300 my-3"></div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Текущая неделя (последняя) */}
|
||||||
|
{currentWeekDataProcessed && (
|
||||||
|
<>
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 mb-2">Текущая неделя</h3>
|
||||||
|
<WeekProgressBar weekData={currentWeekDataProcessed} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user