import React, { useState } from 'react' import LoadingError from './LoadingError' import { useAuth } from './auth/AuthContext' // Функция для форматирования скорa (аналогично formatScore из TaskDetail) const formatScore = (num) => { if (num === 0) return '0' let str = num.toPrecision(4) str = str.replace(/\.?0+$/, '') if (str.includes('e+') || str.includes('e-')) { const numValue = parseFloat(str) if (Math.abs(numValue) >= 10000) { return str } return numValue.toString().replace(/\.?0+$/, '') } return str } // Функция для форматирования текста с заменой плейсхолдеров на nodes const formatEntryText = (text, nodes) => { if (!text || !nodes || nodes.length === 0) { return text } // Создаем map для быстрого доступа к nodes по индексу const nodesMap = {} nodes.forEach(node => { nodesMap[node.index] = node }) // Создаем массив для хранения частей текста и React элементов const parts = [] let lastIndex = 0 let currentText = text // Сначала защищаем экранированные плейсхолдеры const escapedMarkers = {} for (let i = 0; i < 100; i++) { const escaped = `\\$${i}` const marker = `__ESCAPED_DOLLAR_${i}__` if (currentText.includes(escaped)) { escapedMarkers[marker] = escaped currentText = currentText.replace(new RegExp(escaped.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), marker) } } // Заменяем ${0}, ${1}, и т.д. for (let i = 0; i < 100; i++) { const placeholder = `\${${i}}` const regex = new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g') if (nodesMap[i] && currentText.includes(placeholder)) { const node = nodesMap[i] const scoreStr = node.score >= 0 ? `${node.project_name}+${formatScore(node.score)}` : `${node.project_name}-${formatScore(Math.abs(node.score))}` currentText = currentText.replace(regex, `__NODE_${i}__`) // Сохраняем информацию о замене if (!escapedMarkers[`__NODE_${i}__`]) { escapedMarkers[`__NODE_${i}__`] = { type: 'node', text: scoreStr } } } } // Заменяем $0, $1, и т.д. (с конца, чтобы не заменить $1 в $10) for (let i = 99; i >= 0; i--) { if (nodesMap[i]) { const node = nodesMap[i] const scoreStr = node.score >= 0 ? `${node.project_name}+${formatScore(node.score)}` : `${node.project_name}-${formatScore(Math.abs(node.score))}` const regex = new RegExp(`\\$${i}(?!\\d)`, 'g') if (currentText.match(regex)) { currentText = currentText.replace(regex, `__NODE_${i}__`) if (!escapedMarkers[`__NODE_${i}__`]) { escapedMarkers[`__NODE_${i}__`] = { type: 'node', text: scoreStr } } } } } // Разбиваем текст на части и создаем React элементы const result = [] let searchIndex = 0 while (searchIndex < currentText.length) { // Ищем следующий маркер let foundMarker = null let markerIndex = currentText.length // Ищем все маркеры for (const marker in escapedMarkers) { const index = currentText.indexOf(marker, searchIndex) if (index !== -1 && index < markerIndex) { markerIndex = index foundMarker = marker } } // Если нашли маркер if (foundMarker) { // Добавляем текст до маркера if (markerIndex > searchIndex) { result.push(currentText.substring(searchIndex, markerIndex)) } // Добавляем элемент для маркера const markerData = escapedMarkers[foundMarker] if (markerData && markerData.type === 'node') { result.push( {markerData.text} ) } else if (typeof markerData === 'string') { // Это экранированный плейсхолдер result.push(markerData) } searchIndex = markerIndex + foundMarker.length } else { // Больше маркеров нет, добавляем оставшийся текст if (searchIndex < currentText.length) { result.push(currentText.substring(searchIndex)) } break } } return result.length > 0 ? result : currentText } function TodayEntriesList({ data, loading, error, onRetry, onDelete }) { const { authFetch } = useAuth() const [deletingIds, setDeletingIds] = useState(new Set()) const handleDelete = async (entryId) => { if (deletingIds.has(entryId)) return if (!window.confirm('Вы уверены, что хотите удалить эту запись? Это действие нельзя отменить.')) { return } setDeletingIds(prev => new Set(prev).add(entryId)) try { const response = await authFetch(`/api/entries/${entryId}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', }, }) if (!response.ok) { const errorText = await response.text() console.error('Delete error:', response.status, errorText) throw new Error(`Ошибка при удалении записи: ${response.status}`) } // Вызываем callback для обновления данных if (onDelete) { onDelete() } } catch (err) { console.error('Delete failed:', err) alert(err.message || 'Не удалось удалить запись') } finally { setDeletingIds(prev => { const next = new Set(prev) next.delete(entryId) return next }) } } if (loading) { return (