v3.5.3: Убрана группировка бесконечных задач, добавлена иконка бесконечности, улучшен UI модального окна переноса
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 35s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 35s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "play-life-web",
|
"name": "play-life-web",
|
||||||
"version": "3.5.2",
|
"version": "3.5.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -78,9 +78,21 @@ function TaskForm({ onNavigate, taskId }) {
|
|||||||
setRewardMessage(data.task.reward_message || '')
|
setRewardMessage(data.task.reward_message || '')
|
||||||
setProgressionBase(data.task.progression_base ? String(data.task.progression_base) : '')
|
setProgressionBase(data.task.progression_base ? String(data.task.progression_base) : '')
|
||||||
|
|
||||||
|
// Проверяем, является ли задача бесконечной (оба поля = 0)
|
||||||
|
const periodStr = data.task.repetition_period ? data.task.repetition_period.trim() : ''
|
||||||
|
const dateStr = data.task.repetition_date ? data.task.repetition_date.trim() : ''
|
||||||
|
const isPeriodZero = periodStr && (periodStr === '0 day' || periodStr.startsWith('0 '))
|
||||||
|
const isDateZero = dateStr && (dateStr === '0 week' || dateStr.startsWith('0 '))
|
||||||
|
const isInfinite = isPeriodZero && isDateZero
|
||||||
|
|
||||||
|
if (isInfinite) {
|
||||||
|
// Бесконечная задача: показываем 0 в форме
|
||||||
|
setRepetitionPeriodValue('0')
|
||||||
|
setRepetitionPeriodType('day')
|
||||||
|
setRepetitionMode('after')
|
||||||
|
console.log('Loading infinite task: both repetition_period and repetition_date are 0')
|
||||||
|
} else if (data.task.repetition_date) {
|
||||||
// Парсим repetition_date если он есть (приоритет над repetition_period)
|
// Парсим repetition_date если он есть (приоритет над repetition_period)
|
||||||
if (data.task.repetition_date) {
|
|
||||||
const dateStr = data.task.repetition_date.trim()
|
|
||||||
console.log('Parsing repetition_date:', dateStr) // Отладка
|
console.log('Parsing repetition_date:', dateStr) // Отладка
|
||||||
|
|
||||||
// Формат: "N unit" где unit = week, month, year
|
// Формат: "N unit" где unit = week, month, year
|
||||||
@@ -415,15 +427,26 @@ function TaskForm({ onNavigate, taskId }) {
|
|||||||
|
|
||||||
if (repetitionPeriodValue && repetitionPeriodValue.trim() !== '') {
|
if (repetitionPeriodValue && repetitionPeriodValue.trim() !== '') {
|
||||||
const valueStr = repetitionPeriodValue.trim()
|
const valueStr = repetitionPeriodValue.trim()
|
||||||
|
const value = parseInt(valueStr, 10)
|
||||||
|
|
||||||
if (repetitionMode === 'each') {
|
// Проверяем, является ли значение нулевым (бесконечная задача)
|
||||||
|
const isZero = !isNaN(value) && value === 0
|
||||||
|
|
||||||
|
if (isZero) {
|
||||||
|
// Бесконечная задача: устанавливаем оба поля в 0
|
||||||
|
// Для repetition_period используем "0 day"
|
||||||
|
repetitionPeriod = '0 day'
|
||||||
|
// Для repetition_date используем "0 week" (можно использовать любой тип, но week - наиболее универсальный)
|
||||||
|
repetitionDate = '0 week'
|
||||||
|
console.log('Creating infinite task: repetition_period=0 day, repetition_date=0 week')
|
||||||
|
} else if (repetitionMode === 'each') {
|
||||||
// Режим "Каждое" - сохраняем как repetition_date
|
// Режим "Каждое" - сохраняем как repetition_date
|
||||||
// Формат: "N unit" где unit = week, month, year
|
// Формат: "N unit" где unit = week, month, year
|
||||||
repetitionDate = `${valueStr} ${repetitionPeriodType}`
|
repetitionDate = `${valueStr} ${repetitionPeriodType}`
|
||||||
|
repetitionPeriod = null // Убеждаемся, что repetition_period = null
|
||||||
console.log('Sending repetition_date:', repetitionDate)
|
console.log('Sending repetition_date:', repetitionDate)
|
||||||
} else {
|
} else {
|
||||||
// Режим "Через" - сохраняем как repetition_period (INTERVAL)
|
// Режим "Через" - сохраняем как repetition_period (INTERVAL)
|
||||||
const value = parseInt(valueStr, 10)
|
|
||||||
if (!isNaN(value) && value >= 0) {
|
if (!isNaN(value) && value >= 0) {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'minute': 'minute',
|
'minute': 'minute',
|
||||||
@@ -435,6 +458,7 @@ function TaskForm({ onNavigate, taskId }) {
|
|||||||
}
|
}
|
||||||
const unit = typeMap[repetitionPeriodType] || 'day'
|
const unit = typeMap[repetitionPeriodType] || 'day'
|
||||||
repetitionPeriod = `${value} ${unit}`
|
repetitionPeriod = `${value} ${unit}`
|
||||||
|
repetitionDate = null // Убеждаемся, что repetition_date = null
|
||||||
console.log('Sending repetition_period:', repetitionPeriod, 'from value:', repetitionPeriodValue, 'type:', repetitionPeriodType)
|
console.log('Sending repetition_period:', repetitionPeriod, 'from value:', repetitionPeriodValue, 'type:', repetitionPeriodType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -442,6 +466,17 @@ function TaskForm({ onNavigate, taskId }) {
|
|||||||
console.log('No repetition to send (value:', repetitionPeriodValue, 'type:', repetitionPeriodType, 'mode:', repetitionMode, ')')
|
console.log('No repetition to send (value:', repetitionPeriodValue, 'type:', repetitionPeriodType, 'mode:', repetitionMode, ')')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Валидация: если repetition_period != null, то repetition_date == null и наоборот, кроме случая когда они оба == 0
|
||||||
|
if (repetitionPeriod && repetitionDate) {
|
||||||
|
const isPeriodZero = repetitionPeriod.trim() === '0 day' || repetitionPeriod.trim().startsWith('0 ')
|
||||||
|
const isDateZero = repetitionDate.trim() === '0 week' || repetitionDate.trim().startsWith('0 ')
|
||||||
|
if (!isPeriodZero || !isDateZero) {
|
||||||
|
setError('Нельзя одновременно использовать repetition_period и repetition_date, кроме случая бесконечной задачи (оба = 0)')
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
reward_message: rewardMessage.trim() || null,
|
reward_message: rewardMessage.trim() || null,
|
||||||
|
|||||||
@@ -94,6 +94,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-name-wrapper {
|
.task-name-wrapper {
|
||||||
@@ -101,6 +103,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.125rem;
|
gap: 0.125rem;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-name {
|
.task-name {
|
||||||
@@ -110,6 +114,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-next-show-date {
|
.task-next-show-date {
|
||||||
@@ -124,11 +133,23 @@
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-badge-bar {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.task-progression-icon {
|
.task-progression-icon {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-infinite-icon {
|
||||||
|
color: #9ca3af;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.task-actions {
|
.task-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -181,8 +202,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-modal-header {
|
.task-postpone-modal-header {
|
||||||
padding: 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -217,7 +237,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-modal-content {
|
.task-postpone-modal-content {
|
||||||
padding: 1.5rem;
|
padding: 0 1.5rem 1.5rem 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.task-postpone-quick-buttons {
|
.task-postpone-quick-buttons {
|
||||||
|
|||||||
@@ -16,15 +16,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
const [selectedTaskForPostpone, setSelectedTaskForPostpone] = useState(null)
|
const [selectedTaskForPostpone, setSelectedTaskForPostpone] = useState(null)
|
||||||
const [postponeDate, setPostponeDate] = useState('')
|
const [postponeDate, setPostponeDate] = useState('')
|
||||||
const [isPostponing, setIsPostponing] = useState(false)
|
const [isPostponing, setIsPostponing] = useState(false)
|
||||||
// Загружаем состояние раскрытия "Бесконечные" из localStorage (по умолчанию true)
|
|
||||||
const [expandedInfinite, setExpandedInfinite] = useState(() => {
|
|
||||||
try {
|
|
||||||
const saved = localStorage.getItem('taskList_expandedInfinite')
|
|
||||||
return saved ? JSON.parse(saved) : {}
|
|
||||||
} catch {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const [toast, setToast] = useState(null)
|
const [toast, setToast] = useState(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -329,22 +320,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleInfiniteExpanded = (projectName) => {
|
|
||||||
setExpandedInfinite(prev => {
|
|
||||||
const newState = {
|
|
||||||
...prev,
|
|
||||||
[projectName]: !prev[projectName]
|
|
||||||
}
|
|
||||||
// Сохраняем в localStorage
|
|
||||||
try {
|
|
||||||
localStorage.setItem('taskList_expandedInfinite', JSON.stringify(newState))
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Error saving expandedInfinite to localStorage:', err)
|
|
||||||
}
|
|
||||||
return newState
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получаем все проекты из задачи (теперь они приходят в task.project_names)
|
// Получаем все проекты из задачи (теперь они приходят в task.project_names)
|
||||||
const getTaskProjects = (task) => {
|
const getTaskProjects = (task) => {
|
||||||
if (task.project_names && Array.isArray(task.project_names)) {
|
if (task.project_names && Array.isArray(task.project_names)) {
|
||||||
@@ -357,13 +332,42 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
const isZeroPeriod = (intervalStr) => {
|
const isZeroPeriod = (intervalStr) => {
|
||||||
if (!intervalStr) return false
|
if (!intervalStr) return false
|
||||||
|
|
||||||
const parts = intervalStr.trim().split(/\s+/)
|
const trimmed = intervalStr.trim()
|
||||||
|
|
||||||
|
// Проверяем формат времени "00:00:00" или "0:00:00"
|
||||||
|
if (/^\d{1,2}:\d{2}:\d{2}/.test(trimmed)) {
|
||||||
|
const timeParts = trimmed.split(':')
|
||||||
|
if (timeParts.length >= 3) {
|
||||||
|
const hours = parseInt(timeParts[0], 10)
|
||||||
|
const minutes = parseInt(timeParts[1], 10)
|
||||||
|
const seconds = parseInt(timeParts[2], 10)
|
||||||
|
return !isNaN(hours) && !isNaN(minutes) && !isNaN(seconds) &&
|
||||||
|
hours === 0 && minutes === 0 && seconds === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PostgreSQL может возвращать "0 day", "0 days", "0", и т.д.
|
||||||
|
const parts = trimmed.split(/\s+/)
|
||||||
if (parts.length < 1) return false
|
if (parts.length < 1) return false
|
||||||
|
|
||||||
const value = parseInt(parts[0], 10)
|
const value = parseInt(parts[0], 10)
|
||||||
return !isNaN(value) && value === 0
|
return !isNaN(value) && value === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для проверки, является ли repetition_date нулевым
|
||||||
|
const isZeroDate = (dateStr) => {
|
||||||
|
if (!dateStr) return false
|
||||||
|
|
||||||
|
const trimmed = dateStr.trim()
|
||||||
|
const parts = trimmed.split(/\s+/)
|
||||||
|
if (parts.length < 2) return false
|
||||||
|
|
||||||
|
const value = parts[0]
|
||||||
|
// Проверяем, является ли значение "0" (для формата "0 week", "0 month", "0 year")
|
||||||
|
const numValue = parseInt(value, 10)
|
||||||
|
return !isNaN(numValue) && numValue === 0
|
||||||
|
}
|
||||||
|
|
||||||
// Группируем задачи по проектам
|
// Группируем задачи по проектам
|
||||||
const groupedTasks = useMemo(() => {
|
const groupedTasks = useMemo(() => {
|
||||||
const today = new Date()
|
const today = new Date()
|
||||||
@@ -389,30 +393,28 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
nextShowDate.setHours(0, 0, 0, 0)
|
nextShowDate.setHours(0, 0, 0, 0)
|
||||||
isCompleted = nextShowDate.getTime() > today.getTime()
|
isCompleted = nextShowDate.getTime() > today.getTime()
|
||||||
isInfinite = false
|
isInfinite = false
|
||||||
} else if (task.repetition_period && isZeroPeriod(task.repetition_period)) {
|
|
||||||
// Если у задачи период повторения = 0 и нет next_show_at, она в бесконечных
|
|
||||||
isInfinite = true
|
|
||||||
isCompleted = false
|
|
||||||
} else {
|
} else {
|
||||||
// Если нет next_show_at и период не 0, задача в обычных
|
// Бесконечная задача: repetition_period == 0 И (repetition_date == 0 ИЛИ отсутствует)
|
||||||
|
// Для обратной совместимости: если repetition_period = 0, считаем бесконечной
|
||||||
|
const hasZeroPeriod = task.repetition_period && isZeroPeriod(task.repetition_period)
|
||||||
|
const hasZeroDate = task.repetition_date && isZeroDate(task.repetition_date)
|
||||||
|
// Идеально: оба поля = 0, но для старых задач может быть только repetition_period = 0
|
||||||
|
isInfinite = (hasZeroPeriod && hasZeroDate) || (hasZeroPeriod && !task.repetition_date)
|
||||||
isCompleted = false
|
isCompleted = false
|
||||||
isInfinite = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
projects.forEach(projectName => {
|
projects.forEach(projectName => {
|
||||||
if (!groups[projectName]) {
|
if (!groups[projectName]) {
|
||||||
groups[projectName] = {
|
groups[projectName] = {
|
||||||
notCompleted: [],
|
notCompleted: [],
|
||||||
completed: [],
|
completed: []
|
||||||
infinite: []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInfinite) {
|
if (isCompleted) {
|
||||||
groups[projectName].infinite.push(task)
|
|
||||||
} else if (isCompleted) {
|
|
||||||
groups[projectName].completed.push(task)
|
groups[projectName].completed.push(task)
|
||||||
} else {
|
} else {
|
||||||
|
// Бесконечные задачи теперь идут в обычный список
|
||||||
groups[projectName].notCompleted.push(task)
|
groups[projectName].notCompleted.push(task)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -426,6 +428,27 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
const hasSubtasks = task.subtasks_count > 0
|
const hasSubtasks = task.subtasks_count > 0
|
||||||
const showDetailOnCheckmark = hasProgression || hasSubtasks
|
const showDetailOnCheckmark = hasProgression || hasSubtasks
|
||||||
|
|
||||||
|
// Проверяем бесконечную задачу: repetition_period = 0 И (repetition_date = 0 ИЛИ отсутствует)
|
||||||
|
// Для обратной совместимости: если repetition_period = 0, считаем бесконечной
|
||||||
|
const hasZeroPeriod = task.repetition_period && isZeroPeriod(task.repetition_period)
|
||||||
|
const hasZeroDate = task.repetition_date && isZeroDate(task.repetition_date)
|
||||||
|
// Бесконечная задача: repetition_period = 0 И (repetition_date = 0 ИЛИ отсутствует)
|
||||||
|
// Не проверяем next_show_at, так как для бесконечных задач он может быть установлен при выполнении
|
||||||
|
const isInfinite = (hasZeroPeriod && hasZeroDate) || (hasZeroPeriod && !task.repetition_date)
|
||||||
|
|
||||||
|
// Отладка для задачи "Ролик"
|
||||||
|
if (task.name === 'Ролик') {
|
||||||
|
console.log('Task "Ролик":', {
|
||||||
|
name: task.name,
|
||||||
|
repetition_period: task.repetition_period,
|
||||||
|
repetition_date: task.repetition_date,
|
||||||
|
next_show_at: task.next_show_at,
|
||||||
|
hasZeroPeriod,
|
||||||
|
hasZeroDate,
|
||||||
|
isInfinite
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={task.id}
|
key={task.id}
|
||||||
@@ -450,6 +473,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
{hasSubtasks && (
|
{hasSubtasks && (
|
||||||
<span className="task-subtasks-count">(+{task.subtasks_count})</span>
|
<span className="task-subtasks-count">(+{task.subtasks_count})</span>
|
||||||
)}
|
)}
|
||||||
|
<span className="task-badge-bar">
|
||||||
{hasProgression && (
|
{hasProgression && (
|
||||||
<svg
|
<svg
|
||||||
className="task-progression-icon"
|
className="task-progression-icon"
|
||||||
@@ -467,6 +491,24 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
<polyline points="17 6 23 6 23 12"></polyline>
|
<polyline points="17 6 23 6 23 12"></polyline>
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
|
{isInfinite && (
|
||||||
|
<svg
|
||||||
|
className="task-infinite-icon"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
title="Бесконечная задача"
|
||||||
|
>
|
||||||
|
<path d="M12 12c0-2.5-1.5-4.5-3.5-4.5S5 9.5 5 12s1.5 4.5 3.5 4.5S12 14.5 12 12z"/>
|
||||||
|
<path d="M12 12c0 2.5 1.5 4.5 3.5 4.5S19 14.5 19 12s-1.5-4.5-3.5-4.5S12 9.5 12 12z"/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{task.next_show_at && (() => {
|
{task.next_show_at && (() => {
|
||||||
const showDate = new Date(task.next_show_at)
|
const showDate = new Date(task.next_show_at)
|
||||||
@@ -559,12 +601,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
{projectNames.map(projectName => {
|
{projectNames.map(projectName => {
|
||||||
const group = groupedTasks[projectName]
|
const group = groupedTasks[projectName]
|
||||||
const hasCompleted = group.completed.length > 0
|
const hasCompleted = group.completed.length > 0
|
||||||
const hasInfinite = group.infinite.length > 0
|
|
||||||
const isCompletedExpanded = expandedCompleted[projectName]
|
const isCompletedExpanded = expandedCompleted[projectName]
|
||||||
// По умолчанию бесконечные раскрыты (true), если не сохранено иное
|
|
||||||
const isInfiniteExpanded = expandedInfinite[projectName] !== undefined
|
|
||||||
? expandedInfinite[projectName]
|
|
||||||
: true
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={projectName} className="project-group">
|
<div key={projectName} className="project-group">
|
||||||
@@ -572,33 +609,13 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
<h3 className="project-group-title">{projectName}</h3>
|
<h3 className="project-group-title">{projectName}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Обычные задачи */}
|
{/* Обычные задачи (включая бесконечные) */}
|
||||||
{group.notCompleted.length > 0 && (
|
{group.notCompleted.length > 0 && (
|
||||||
<div className="task-group">
|
<div className="task-group">
|
||||||
{group.notCompleted.map(renderTaskItem)}
|
{group.notCompleted.map(renderTaskItem)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Бесконечные задачи */}
|
|
||||||
{hasInfinite && (
|
|
||||||
<div className="completed-section">
|
|
||||||
<button
|
|
||||||
className="completed-toggle"
|
|
||||||
onClick={() => toggleInfiniteExpanded(projectName)}
|
|
||||||
>
|
|
||||||
<span className="completed-toggle-icon">
|
|
||||||
{isInfiniteExpanded ? '▼' : '▶'}
|
|
||||||
</span>
|
|
||||||
<span>Бесконечные ({group.infinite.length})</span>
|
|
||||||
</button>
|
|
||||||
{isInfiniteExpanded && (
|
|
||||||
<div className="task-group completed-tasks">
|
|
||||||
{group.infinite.map(renderTaskItem)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Выполненные задачи */}
|
{/* Выполненные задачи */}
|
||||||
{hasCompleted && (
|
{hasCompleted && (
|
||||||
<div className="completed-section">
|
<div className="completed-section">
|
||||||
@@ -619,7 +636,7 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{group.notCompleted.length === 0 && !hasCompleted && !hasInfinite && (
|
{group.notCompleted.length === 0 && !hasCompleted && (
|
||||||
<div className="empty-group">Нет задач в этой группе</div>
|
<div className="empty-group">Нет задач в этой группе</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user