import { Habit, SafeUser, User, Permission } from '@/lib/types' import { useAtom } from 'jotai' import { settingsAtom, pomodoroAtom, browserSettingsAtom, usersAtom, currentUserAtom } from '@/lib/atoms' import { getTodayInTimezone, isSameDate, t2d, d2t, getNow, d2s, getCompletionsForToday, isTaskOverdue, convertMachineReadableFrequencyToHumanReadable } from '@/lib/utils' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu' import { useHabits } from '@/hooks/useHabits' import { currentUserAtom, settingsAtom, usersAtom } from '@/lib/atoms' import { Habit, User } from '@/lib/types' import { convertMachineReadableFrequencyToHumanReadable, getCompletionsForToday, hasPermission, isTaskOverdue } from '@/lib/utils' import { useAtom } from 'jotai' import { Check, Coins, Edit, MoreVertical, Pin, Undo2 } from 'lucide-react' import { useTranslations } from 'next-intl' import { INITIAL_RECURRENCE_RULE, RECURRENCE_RULE_MAP } from '@/lib/constants' import { DateTime } from 'luxon' import { Avatar, AvatarFallback, AvatarImage } from './ui/avatar' import { hasPermission } from '@/lib/utils' import { HabitContextMenuItems } from './HabitContextMenuItems' interface HabitItemProps { habit: Habit onEdit: () => void onDelete: () => void } const renderUserAvatars = (habit: Habit, currentUser: User | null, usersData: { users: User[] }) => { if (!habit.userIds || habit.userIds.length <= 1) return <>; return (
{habit.userIds?.filter((u) => u !== currentUser?.id).map(userId => { const user = usersData.users.find(u => u.id === userId) if (!user) return <>; return ( {user.username[0]} ) })}
); }; export default function HabitItem({ habit, onEdit, onDelete }: HabitItemProps) { const { completeHabit, undoComplete } = useHabits() const [settings] = useAtom(settingsAtom) const completionsToday = getCompletionsForToday({ habit, timezone: settings.system.timezone }) const target = habit.targetCompletions || 1 const isCompletedToday = completionsToday >= target const [isHighlighted, setIsHighlighted] = useState(false) const t = useTranslations('HabitItem'); const [usersData] = useAtom(usersAtom) const pathname = usePathname(); const [currentUser] = useAtom(currentUserAtom) const canWrite = hasPermission(currentUser, 'habit', 'write') const canInteract = hasPermission(currentUser, 'habit', 'interact') useEffect(() => { const params = new URLSearchParams(window.location.search) const highlightId = params.get('highlight') if (highlightId === habit.id) { setIsHighlighted(true) // Scroll the element into view after a short delay to ensure rendering setTimeout(() => { const element = document.getElementById(`habit-${habit.id}`) if (element) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }) } }, 100) // Remove highlight after animation const timer = setTimeout(() => setIsHighlighted(false), 2000) return () => clearTimeout(timer) } }, [habit.id]) return (
{habit.pinned && ( )} {habit.name}
{isTaskOverdue(habit, settings.system.timezone) && ( {t('overdue')} )}
{renderUserAvatars(habit, currentUser as User, usersData)}
{(habit.description || habit.drawing) && (
{habit.description && ( {habit.description} )} {habit.drawing && (
)}
)}

{t('whenLabel', { frequency: convertMachineReadableFrequencyToHumanReadable({ frequency: habit.frequency, isRecurRule, timezone: settings.system.timezone }) })}

{t('coinsPerCompletion', { count: habit.coinReward })}
{completionsToday > 0 && !habit.archived && ( )}
{!habit.archived && ( )}
) }