v3.1.0: Оптимизация загрузки списка задач - все данные в одном запросе, добавлены индикаторы подзадач и прогрессии
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 40s

This commit is contained in:
poignatov
2026-01-06 14:54:37 +03:00
parent 28d8148665
commit 0ea531889d
5 changed files with 138 additions and 114 deletions

View File

@@ -89,11 +89,31 @@
color: #8b5cf6;
}
.task-name-container {
flex: 1;
display: flex;
align-items: center;
gap: 0.5rem;
}
.task-name {
font-size: 1rem;
font-weight: 500;
color: #1f2937;
flex: 1;
display: flex;
align-items: center;
gap: 0.25rem;
}
.task-subtasks-count {
color: #9ca3af;
font-size: 0.875rem;
font-weight: 400;
}
.task-progression-icon {
color: #9ca3af;
flex-shrink: 0;
}
.task-actions {

View File

@@ -10,8 +10,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
const { authFetch } = useAuth()
// Инициализируем tasks из data, если data есть, иначе пустой массив
const [tasks, setTasks] = useState(() => data && Array.isArray(data) ? data : [])
const [taskDetails, setTaskDetails] = useState({})
const [loadingDetails, setLoadingDetails] = useState(false)
const [selectedTaskForDetail, setSelectedTaskForDetail] = useState(null)
const [isCompleting, setIsCompleting] = useState(false)
const [expandedCompleted, setExpandedCompleted] = useState({})
@@ -25,9 +23,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
}
})
const [toast, setToast] = useState(null)
// Для отслеживания изменений в списке задач (чтобы не перезагружать детали без необходимости)
const lastTaskIdsRef = useRef('')
useEffect(() => {
if (data) {
@@ -38,64 +33,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
// Загрузка данных управляется из App.jsx через loadTabData
// TaskList не инициирует загрузку самостоятельно
// Загружаем детали для всех задач
// Оптимизация: загружаем только если список задач изменился (по id и last_completed_at)
useEffect(() => {
if (!tasks || tasks.length === 0) {
setTaskDetails({})
lastTaskIdsRef.current = ''
return
}
// Создаем ключ из id и last_completed_at всех задач
const taskKey = tasks.map(t => `${t.id}:${t.last_completed_at || ''}`).sort().join(',')
// Если ключ не изменился, не перезагружаем детали
if (taskKey === lastTaskIdsRef.current) {
return
}
lastTaskIdsRef.current = taskKey
const loadTaskDetails = async () => {
// Не показываем индикатор загрузки если детали уже есть (фоновое обновление)
const hasExistingDetails = Object.keys(taskDetails).length > 0
if (!hasExistingDetails) {
setLoadingDetails(true)
}
try {
const detailPromises = tasks.map(async (task) => {
try {
const response = await authFetch(`${API_URL}/${task.id}`)
if (response.ok) {
const detail = await response.json()
return { taskId: task.id, detail }
}
} catch (err) {
console.error(`Error loading task detail for ${task.id}:`, err)
}
return null
})
const details = await Promise.all(detailPromises)
const detailsMap = {}
details.forEach(item => {
if (item) {
detailsMap[item.taskId] = item.detail
}
})
setTaskDetails(detailsMap)
} catch (err) {
console.error('Error loading task details:', err)
} finally {
setLoadingDetails(false)
}
}
loadTaskDetails()
}, [tasks, authFetch])
const handleTaskClick = (task) => {
onNavigate?.('task-form', { taskId: task.id })
}
@@ -103,9 +40,8 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
const handleCheckmarkClick = async (task, e) => {
e.stopPropagation()
const detail = taskDetails[task.id]
const hasProgression = detail?.task?.progression_base != null
const hasSubtasks = detail?.subtasks && detail.subtasks.length > 0
const hasProgression = task.has_progression || task.progression_base != null
const hasSubtasks = task.subtasks_count > 0
if (hasProgression || hasSubtasks) {
// Открываем экран details
@@ -174,36 +110,12 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
})
}
// Получаем все проекты из наград задачи и подзадач
// Получаем все проекты из задачи (теперь они приходят в task.project_names)
const getTaskProjects = (task) => {
const projects = new Set()
const detail = taskDetails[task.id]
if (detail) {
// Проекты из основной задачи
if (detail.rewards) {
detail.rewards.forEach(reward => {
if (reward.project_name) {
projects.add(reward.project_name)
}
})
}
// Проекты из подзадач
if (detail.subtasks) {
detail.subtasks.forEach(subtask => {
if (subtask.rewards) {
subtask.rewards.forEach(reward => {
if (reward.project_name) {
projects.add(reward.project_name)
}
})
}
})
}
if (task.project_names && Array.isArray(task.project_names)) {
return task.project_names
}
return Array.from(projects)
return []
}
// Функция для проверки, является ли период нулевым
@@ -340,12 +252,11 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
})
return groups
}, [tasks, taskDetails])
}, [tasks])
const renderTaskItem = (task) => {
const detail = taskDetails[task.id]
const hasProgression = detail?.task?.progression_base != null
const hasSubtasks = detail?.subtasks && detail.subtasks.length > 0
const hasProgression = task.has_progression || task.progression_base != null
const hasSubtasks = task.subtasks_count > 0
const showDetailOnCheckmark = hasProgression || hasSubtasks
return (
@@ -365,7 +276,31 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
<path d="M6 10 L9 13 L14 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="checkmark-check" />
</svg>
</div>
<div className="task-name">{task.name}</div>
<div className="task-name-container">
<div className="task-name">
{task.name}
{hasSubtasks && (
<span className="task-subtasks-count">(+{task.subtasks_count})</span>
)}
</div>
{hasProgression && (
<svg
className="task-progression-icon"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
title="Задача с прогрессией"
>
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18"></polyline>
<polyline points="17 6 23 6 23 12"></polyline>
</svg>
)}
</div>
<div className="task-actions">
<span className="task-completed-count">{task.completed}</span>
</div>
@@ -408,10 +343,6 @@ function TaskList({ onNavigate, data, loading, backgroundLoading, onRefresh }) {
Добавить
</button>
{loadingDetails && tasks.length > 0 && (
<div className="loading-details">Загрузка деталей задач...</div>
)}
{projectNames.length === 0 && !loading && tasks.length === 0 && (
<div className="empty-state">
<p>Задач пока нет. Добавьте задачу через кнопку "Добавить".</p>