Обновление интерфейса задач и версия 3.21.0
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 49s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 49s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "3.20.4",
|
"version": "3.21.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import React, { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import CurrentWeek from './components/CurrentWeek'
|
import CurrentWeek from './components/CurrentWeek'
|
||||||
import FullStatistics from './components/FullStatistics'
|
import FullStatistics from './components/FullStatistics'
|
||||||
import ProjectPriorityManager from './components/ProjectPriorityManager'
|
import ProjectPriorityManager from './components/ProjectPriorityManager'
|
||||||
@@ -120,6 +120,10 @@ function AppContent() {
|
|||||||
const [wordsRefreshTrigger, setWordsRefreshTrigger] = useState(0)
|
const [wordsRefreshTrigger, setWordsRefreshTrigger] = useState(0)
|
||||||
const [wishlistRefreshTrigger, setWishlistRefreshTrigger] = useState(0)
|
const [wishlistRefreshTrigger, setWishlistRefreshTrigger] = useState(0)
|
||||||
|
|
||||||
|
// Состояние для сохранения позиции скролла каждого таба
|
||||||
|
const [scrollPositions, setScrollPositions] = useState({})
|
||||||
|
|
||||||
|
|
||||||
// Восстанавливаем последний выбранный таб после перезагрузки
|
// Восстанавливаем последний выбранный таб после перезагрузки
|
||||||
const [isInitialized, setIsInitialized] = useState(false)
|
const [isInitialized, setIsInitialized] = useState(false)
|
||||||
|
|
||||||
@@ -624,6 +628,15 @@ function AppContent() {
|
|||||||
setTabParams({})
|
setTabParams({})
|
||||||
updateUrl('full', {}, activeTab)
|
updateUrl('full', {}, activeTab)
|
||||||
} else if (tab !== activeTab || tab === 'task-form' || tab === 'wishlist-form') {
|
} else if (tab !== activeTab || tab === 'task-form' || tab === 'wishlist-form') {
|
||||||
|
// Сохраняем позицию скролла для текущего таба перед переходом
|
||||||
|
const scrollContainer = document.querySelector('.flex-1.overflow-y-auto')
|
||||||
|
if (scrollContainer && mainTabs.includes(activeTab)) {
|
||||||
|
setScrollPositions(prev => ({
|
||||||
|
...prev,
|
||||||
|
[activeTab]: scrollContainer.scrollTop
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// Для task-form и wishlist-form всегда обновляем параметры, даже если это тот же таб
|
// Для task-form и wishlist-form всегда обновляем параметры, даже если это тот же таб
|
||||||
markTabAsLoaded(tab)
|
markTabAsLoaded(tab)
|
||||||
|
|
||||||
@@ -722,6 +735,22 @@ function AppContent() {
|
|||||||
prevActiveTabRef.current = activeTab
|
prevActiveTabRef.current = activeTab
|
||||||
}, [activeTab, loadedTabs, loadTabData])
|
}, [activeTab, loadedTabs, loadTabData])
|
||||||
|
|
||||||
|
// Восстанавливаем позицию скролла при возвращении на таб
|
||||||
|
useEffect(() => {
|
||||||
|
if (mainTabs.includes(activeTab) && scrollPositions[activeTab] !== undefined) {
|
||||||
|
// Используем setTimeout, чтобы DOM успел обновиться
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
const scrollContainer = document.querySelector('.flex-1.overflow-y-auto')
|
||||||
|
if (scrollContainer) {
|
||||||
|
scrollContainer.scrollTop = scrollPositions[activeTab]
|
||||||
|
}
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return () => clearTimeout(timeoutId)
|
||||||
|
}
|
||||||
|
}, [activeTab, scrollPositions])
|
||||||
|
|
||||||
|
|
||||||
// Определяем общее состояние загрузки и ошибок для кнопки Refresh
|
// Определяем общее состояние загрузки и ошибок для кнопки Refresh
|
||||||
const isAnyLoading = currentWeekLoading || fullStatisticsLoading || prioritiesLoading || isRefreshing
|
const isAnyLoading = currentWeekLoading || fullStatisticsLoading || prioritiesLoading || isRefreshing
|
||||||
const hasAnyError = currentWeekError || fullStatisticsError || prioritiesError
|
const hasAnyError = currentWeekError || fullStatisticsError || prioritiesError
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import React from 'react'
|
||||||
import ProjectProgressBar from './ProjectProgressBar'
|
import ProjectProgressBar from './ProjectProgressBar'
|
||||||
import LoadingError from './LoadingError'
|
import LoadingError from './LoadingError'
|
||||||
import { getAllProjectsSorted, getProjectColor } from '../utils/projectUtils'
|
import { getAllProjectsSorted, getProjectColor } from '../utils/projectUtils'
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
|||||||
// Парсим repetition_period если он есть
|
// Парсим repetition_period если он есть
|
||||||
setRepetitionMode('after')
|
setRepetitionMode('after')
|
||||||
const periodStr = data.task.repetition_period.trim()
|
const periodStr = data.task.repetition_period.trim()
|
||||||
console.log('Parsing repetition_period:', periodStr, 'Full task data:', data.task) // Отладка
|
|
||||||
|
|
||||||
// PostgreSQL может возвращать INTERVAL в разных форматах:
|
// PostgreSQL может возвращать INTERVAL в разных форматах:
|
||||||
// - "1 day" / "1 days" / "10 days"
|
// - "1 day" / "1 days" / "10 days"
|
||||||
@@ -194,8 +193,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
|||||||
const value = parseInt(match[1], 10)
|
const value = parseInt(match[1], 10)
|
||||||
const unit = match[2].toLowerCase()
|
const unit = match[2].toLowerCase()
|
||||||
|
|
||||||
console.log('Matched value:', value, 'unit:', unit) // Отладка
|
|
||||||
|
|
||||||
if (!isNaN(value) && value >= 0) {
|
if (!isNaN(value) && value >= 0) {
|
||||||
// Преобразуем единицы PostgreSQL в наш формат
|
// Преобразуем единицы PostgreSQL в наш формат
|
||||||
if (unit.startsWith('minute')) {
|
if (unit.startsWith('minute')) {
|
||||||
@@ -293,8 +290,6 @@ function TaskForm({ onNavigate, taskId, wishlistId, isTest: isTestFromProps = fa
|
|||||||
console.log('Failed to parse repetition_period:', periodStr) // Отладка
|
console.log('Failed to parse repetition_period:', periodStr) // Отладка
|
||||||
setRepetitionPeriodValue('')
|
setRepetitionPeriodValue('')
|
||||||
setRepetitionPeriodType('day')
|
setRepetitionPeriodType('day')
|
||||||
} else {
|
|
||||||
console.log('Successfully parsed repetition_period - value will be set') // Отладка
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('No repetition_period or repetition_date in task data') // Отладка
|
console.log('No repetition_period or repetition_date in task data') // Отладка
|
||||||
|
|||||||
@@ -556,10 +556,43 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
onClick={(e) => handleCheckmarkClick(task, e)}
|
onClick={(e) => handleCheckmarkClick(task, e)}
|
||||||
title={isTest ? 'Запустить тест' : (showDetailOnCheckmark ? 'Открыть детали' : 'Выполнить задачу')}
|
title={isTest ? 'Запустить тест' : (showDetailOnCheckmark ? 'Открыть детали' : 'Выполнить задачу')}
|
||||||
>
|
>
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
{isTest ? (
|
||||||
<circle cx="10" cy="10" r="9" stroke="currentColor" strokeWidth="2" fill="none" className="checkmark-circle" />
|
<svg
|
||||||
<path d="M6 10 L9 13 L14 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="checkmark-check" />
|
width="20"
|
||||||
</svg>
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
|
||||||
|
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
|
||||||
|
</svg>
|
||||||
|
) : isWishlist ? (
|
||||||
|
<svg
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="20 12 20 22 4 22 4 12"></polyline>
|
||||||
|
<rect x="2" y="7" width="20" height="5"></rect>
|
||||||
|
<line x1="12" y1="22" x2="12" y2="7"></line>
|
||||||
|
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
|
||||||
|
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<circle cx="10" cy="10" r="9" stroke="currentColor" strokeWidth="2" fill="none" className="checkmark-circle" />
|
||||||
|
<path d="M6 10 L9 13 L14 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="checkmark-check" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="task-name-container">
|
<div className="task-name-container">
|
||||||
<div className="task-name-wrapper">
|
<div className="task-name-wrapper">
|
||||||
@@ -569,43 +602,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, error, onRetry
|
|||||||
<span className="task-subtasks-count">(+{task.subtasks_count})</span>
|
<span className="task-subtasks-count">(+{task.subtasks_count})</span>
|
||||||
)}
|
)}
|
||||||
<span className="task-badge-bar">
|
<span className="task-badge-bar">
|
||||||
{isWishlist && (
|
|
||||||
<svg
|
|
||||||
className="task-wishlist-icon"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
title="Связано с желанием"
|
|
||||||
>
|
|
||||||
<polyline points="20 12 20 22 4 22 4 12"></polyline>
|
|
||||||
<rect x="2" y="7" width="20" height="5"></rect>
|
|
||||||
<line x1="12" y1="22" x2="12" y2="7"></line>
|
|
||||||
<path d="M12 7H7.5a2.5 2.5 0 0 1 0-5C11 2 12 7 12 7z"></path>
|
|
||||||
<path d="M12 7h4.5a2.5 2.5 0 0 0 0-5C13 2 12 7 12 7z"></path>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
{isTest && (
|
|
||||||
<svg
|
|
||||||
className="task-test-icon"
|
|
||||||
width="16"
|
|
||||||
height="16"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
title="Тест"
|
|
||||||
>
|
|
||||||
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"></path>
|
|
||||||
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"></path>
|
|
||||||
</svg>
|
|
||||||
)}
|
|
||||||
{hasProgression && (
|
{hasProgression && (
|
||||||
<svg
|
<svg
|
||||||
className="task-progression-icon"
|
className="task-progression-icon"
|
||||||
|
|||||||
Reference in New Issue
Block a user