Прогресс на сегодня
{completedCount} / {totalToday}{stats.today_completed}
Выполнено
{stats.active_habits}
Активных
Задачи на сегодня
Нет задач на сегодня
Привычки
{habitsLoading ? (Свободный день!
На сегодня нет запланированных привычек.
import { useState, useEffect, useMemo, useRef } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { motion, AnimatePresence } from 'framer-motion' import { Check, Flame, TrendingUp, Zap, Sparkles, Undo2, Plus, Calendar, AlertTriangle, LogOut, Snowflake } from 'lucide-react' import { format, startOfWeek, differenceInDays, parseISO, isToday, isTomorrow, isPast, startOfDay, isBefore, isAfter } from 'date-fns' import { ru } from 'date-fns/locale' import { habitsApi } from '../api/habits' import { tasksApi } from '../api/tasks' import { useAuthStore } from '../store/auth' import Navigation from '../components/Navigation' import CreateTaskModal from '../components/CreateTaskModal' import LogHabitModal from '../components/LogHabitModal' import clsx from 'clsx' // Check if habit is frozen on a specific date function isHabitFrozenOnDate(habit, freezes, date) { if (!freezes || freezes.length === 0) return false const checkDate = startOfDay(date) return freezes.some(freeze => { const start = startOfDay(parseISO(freeze.start_date)) const end = startOfDay(parseISO(freeze.end_date)) return !isBefore(checkDate, start) && !isAfter(checkDate, end) }) } // Определение "сегодняшних" привычек function shouldShowToday(habit, lastLogDate, freezes) { const today = startOfDay(new Date()) const dayOfWeek = today.getDay() || 7 if (isHabitFrozenOnDate(habit, freezes, today)) return false const startDate = habit.start_date ? startOfDay(parseISO(habit.start_date)) : startOfDay(parseISO(habit.created_at)) if (today < startDate) return false if (habit.frequency === "daily") return true if (habit.frequency === "weekly") { if (habit.target_days && habit.target_days.length > 0) { return habit.target_days.includes(dayOfWeek) } if (!lastLogDate) return true const weekStart = startOfWeek(today, { weekStartsOn: 1 }) const lastLog = typeof lastLogDate === "string" ? parseISO(lastLogDate) : lastLogDate return lastLog < weekStart } if (habit.frequency === "interval" && habit.target_count > 0) { const daysSinceStart = differenceInDays(today, startDate) return daysSinceStart % habit.target_count === 0 } if (habit.frequency === "custom" && habit.target_count > 0) { if (!lastLogDate) return today >= startDate const lastLog = typeof lastLogDate === "string" ? parseISO(lastLogDate) : lastLogDate const daysSinceLastLog = differenceInDays(today, startOfDay(lastLog)) return daysSinceLastLog >= habit.target_count } return true } function formatDueDate(dateStr) { if (!dateStr) return null const date = parseISO(dateStr) if (isToday(date)) return 'Сегодня' if (isTomorrow(date)) return 'Завтра' return format(date, 'd MMM', { locale: ru }) } export default function Home() { const [todayLogs, setTodayLogs] = useState({}) const [lastLogDates, setLastLogDates] = useState({}) const [habitFreezes, setHabitFreezes] = useState({}) const [habitLogs, setHabitLogs] = useState({}) const [showCreateTask, setShowCreateTask] = useState(false) const [logHabitModal, setLogHabitModal] = useState({ open: false, habit: null }) const queryClient = useQueryClient() const { user, logout } = useAuthStore() const { data: habits = [], isLoading: habitsLoading } = useQuery({ queryKey: ['habits'], queryFn: habitsApi.list, }) const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: habitsApi.getStats, }) const { data: todayTasks = [], isLoading: tasksLoading } = useQuery({ queryKey: ['tasks-today'], queryFn: tasksApi.today, }) useEffect(() => { if (habits.length > 0) { loadTodayLogs() loadHabitFreezes() } }, [habits]) const loadTodayLogs = async () => { const today = format(new Date(), 'yyyy-MM-dd') const logsMap = {} const lastDates = {} const allLogs = {} await Promise.all(habits.map(async (habit) => { try { const logs = await habitsApi.getLogs(habit.id, 90) allLogs[habit.id] = logs.map(l => l.date) if (logs.length > 0) { const lastLog = logs[0] const logDate = lastLog.date.split('T')[0] lastDates[habit.id] = logDate if (logDate === today) logsMap[habit.id] = lastLog.id } } catch (e) { console.error('Error loading logs for habit', habit.id, e) } })) setTodayLogs(logsMap) setLastLogDates(lastDates) setHabitLogs(allLogs) } const loadHabitFreezes = async () => { const freezesMap = {} await Promise.all(habits.map(async (habit) => { try { const freezes = await habitsApi.getFreezes(habit.id) freezesMap[habit.id] = freezes } catch (e) { freezesMap[habit.id] = [] } })) setHabitFreezes(freezesMap) } const logMutation = useMutation({ mutationFn: ({ habitId, date }) => habitsApi.log(habitId, date ? { date } : {}), onSuccess: (data, { habitId, date }) => { const logDate = date || format(new Date(), 'yyyy-MM-dd') const today = format(new Date(), 'yyyy-MM-dd') if (logDate === today) setTodayLogs(prev => ({ ...prev, [habitId]: data.id })) setLastLogDates(prev => ({ ...prev, [habitId]: logDate })) setHabitLogs(prev => ({ ...prev, [habitId]: [...(prev[habitId] || []), logDate] })) queryClient.invalidateQueries({ queryKey: ['habits'] }) queryClient.invalidateQueries({ queryKey: ['stats'] }) }, }) const deleteLogMutation = useMutation({ mutationFn: ({ habitId, logId }) => habitsApi.deleteLog(habitId, logId), onSuccess: (_, { habitId }) => { setTodayLogs(prev => { const newLogs = { ...prev } delete newLogs[habitId] return newLogs }) loadTodayLogs() queryClient.invalidateQueries({ queryKey: ['habits'] }) queryClient.invalidateQueries({ queryKey: ['stats'] }) }, }) const completeTaskMutation = useMutation({ mutationFn: (id) => tasksApi.complete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks-today'] }) queryClient.invalidateQueries({ queryKey: ['tasks'] }) }, }) const uncompleteTaskMutation = useMutation({ mutationFn: (id) => tasksApi.uncomplete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['tasks-today'] }) queryClient.invalidateQueries({ queryKey: ['tasks'] }) }, }) const handleToggleComplete = (habitId) => { if (todayLogs[habitId]) { deleteLogMutation.mutate({ habitId, logId: todayLogs[habitId] }) } else { logMutation.mutate({ habitId }) } } const handleLogHabitDate = async (habitId, date) => { await logMutation.mutateAsync({ habitId, date }) } const handleToggleTask = (task) => { if (task.completed) uncompleteTaskMutation.mutate(task.id) else completeTaskMutation.mutate(task.id) } const todayHabits = useMemo(() => { return habits.filter(habit => shouldShowToday(habit, lastLogDates[habit.id], habitFreezes[habit.id])) }, [habits, lastLogDates, habitFreezes]) const frozenHabits = useMemo(() => { const today = startOfDay(new Date()) return habits.filter(habit => isHabitFrozenOnDate(habit, habitFreezes[habit.id], today)) }, [habits, habitFreezes]) const completedCount = Object.keys(todayLogs).length const totalToday = todayHabits.length const today = format(new Date(), 'EEEE, d MMMM', { locale: ru }) const activeTasks = todayTasks.filter(t => !t.completed) return (
{today}
{stats.today_completed}
Выполнено
{stats.active_habits}
Активных
Нет задач на сегодня
На сегодня нет запланированных привычек.
{habit.description}
}