Untitled

 avatar
unknown
plain_text
24 days ago
8.3 kB
3
Indexable
function HabitTracker() {
  // State Management
  const [activeView, setActiveView] = dc.useState('weekly');
  const [selectedDate, setSelectedDate] = dc.useState(dc.luxon.DateTime.now());
  const [editingTime, setEditingTime] = dc.useState(null);
  const [currentPage, setCurrentPage] = dc.useState(0);

  // Data Queries and Utility Functions
  const dailyNotes = dc.useQuery(`
    @page 
    AND path("002 Journal")
  `);

  const sortedNotes = dc.useMemo(() => {
    return [...dailyNotes].sort((a, b) => b.$name.localeCompare(a.$name));
  }, [dailyNotes]);

  const getNotesForPeriod = (startDate) => {
    return sortedNotes.filter(note => {
      const noteDate = dc.luxon.DateTime.fromISO(note.$name);
      return noteDate >= startDate;
    });
  };

  // Add new function to get notes for specific date range
  const getNotesForDateRange = (startDate, endDate) => {
    return sortedNotes.filter(note => {
      const noteDate = dc.luxon.DateTime.fromISO(note.$name);
      // Use startOf('day') and endOf('day') to ensure full day coverage
      return noteDate >= startDate.startOf('day') && noteDate <= endDate.endOf('day');
    });
  };

  const last30DaysNotes = dc.useMemo(() =>
    getNotesForPeriod(selectedDate.minus({ days: 30 })),
    [sortedNotes, selectedDate]
  );

  const yearToDateNotes = dc.useMemo(() =>
    getNotesForPeriod(selectedDate.startOf('year')),
    [sortedNotes, selectedDate]
  );

  const currentMonthNotes = dc.useMemo(() =>
    getNotesForPeriod(selectedDate.startOf('month')),
    [sortedNotes, selectedDate]
  );

  const previousMonthNotes = dc.useMemo(() =>
    sortedNotes.filter(note => {
      const noteDate = dc.luxon.DateTime.fromISO(note.$name);
      const monthAgo = selectedDate.minus({ months: 1 });
      return noteDate >= monthAgo && noteDate < selectedDate.startOf('month');
    }),
    [sortedNotes, selectedDate]
  );

  const getHabitStatus = (entry, habitId) => {
    const habit = HABITS.find(h => h.id === habitId);
    const value = entry?.value(habitId) ?? 0;
    return value >= habit.defaultDuration;
  };

  const getHabitDuration = (entry, habitId) => {
    return entry?.value(habitId) ?? null;
  };

  const calculateCompletedHabits = (entry) => {
    if (!entry) return 0;
    return HABITS.reduce((count, habit) =>
      count + (getHabitStatus(entry, habit.id) ? 1 : 0), 0);
  };

  const calculatePerfectDays = (notes) => {
    return notes.reduce((count, note) =>
      count + (calculateCompletedHabits(note) === HABITS.length ? 1 : 0), 0);
  };

  const calculateTrends = () => {
    const trends = {
      last30Days: {
        perfectDays: calculatePerfectDays(last30DaysNotes),
        habitMetrics: {}
      },
      yearToDate: {
        perfectDays: calculatePerfectDays(yearToDateNotes),
        habitMetrics: {}
      },
      currentMonth: {
        perfectDays: calculatePerfectDays(currentMonthNotes),
        progress: 0
      }
    };

    trends.currentMonth.progress = (trends.currentMonth.perfectDays / GOALS.perfectDays.monthly) * 100;

    HABITS.forEach(habit => {
      const last30Total = last30DaysNotes.reduce((sum, note) => {
        const value = note?.value(habit.id);
        const numValue = value ? Number(value) : 0;
        return sum + (isNaN(numValue) ? 0 : numValue);
      }, 0);

      const ytdTotal = yearToDateNotes.reduce((sum, note) => {
        const value = note?.value(habit.id);
        const numValue = value ? Number(value) : 0;
        return sum + (isNaN(numValue) ? 0 : numValue);
      }, 0);

      const previousMonthTotal = previousMonthNotes.reduce((sum, note) => {
        const value = note?.value(habit.id);
        const numValue = value ? Number(value) : 0;
        return sum + (isNaN(numValue) ? 0 : numValue);
      }, 0);

      trends.last30Days.habitMetrics[habit.id] = {
        total: last30Total,
        previousPeriodTotal: previousMonthTotal
      };

      trends.yearToDate.habitMetrics[habit.id] = {
        total: ytdTotal
      };
    });

    return trends;
  };

  // Get current week's notes based on selected date
  const currentWeekNotes = dc.useMemo(() =>
    sortedNotes.filter(note => {
      const noteDate = dc.luxon.DateTime.fromISO(note.$name);
      const startOfWeek = selectedDate.startOf('week');
      const endOfWeek = selectedDate.endOf('week');
      return noteDate >= startOfWeek && noteDate <= endOfWeek;
    }),
    [sortedNotes, selectedDate]
  );

  // Action Handlers
  async function updateHabit(entry, habitId) {
    const file = app.vault.getAbstractFileByPath(entry.$path);
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      const habit = HABITS.find(h => h.id === habitId);
      const currentValue = frontmatter[habitId];
      frontmatter[habitId] = currentValue ? 0 : habit.defaultDuration;
    });
  }

  async function updateHabitDuration(entry, habitId, duration) {
    const file = app.vault.getAbstractFileByPath(entry.$path);
    await app.fileManager.processFrontMatter(file, (frontmatter) => {
      frontmatter[habitId] = parseInt(duration) || 0;
    });
    setEditingTime(null);
  }

  const navigateDate = (direction) => {
    setSelectedDate(prev => prev.plus({ days: direction }));
  };

  // Main Layout
  return (
  <div style={{
    width: '100%',
    margin: '0 auto',
    padding: '24px',
    display: 'flex',
    flexDirection: 'column',
    gap: '24px'
  }}>
    <NavigationControls
      selectedDate={selectedDate}
      navigateDate={navigateDate}
      activeView={activeView}
      setActiveView={setActiveView}
    />

    <StyledCard>
      <CalendarView
        selectedDate={selectedDate}
        sortedNotes={getNotesForDateRange(selectedDate.minus({ days: 6 }), selectedDate)}
        getHabitStatus={getHabitStatus}
        calculateCompletedHabits={calculateCompletedHabits}
        updateHabit={updateHabit}
        getHabitDuration={getHabitDuration}
        editingTime={editingTime}
        setEditingTime={setEditingTime}
        updateHabitDuration={updateHabitDuration}
      />

      <div style={{
        display: 'flex',
        justifyContent: 'center',
        gap: '16px',
        marginTop: '16px',
        paddingTop: '16px',
        borderTop: '1px solid var(--background-modifier-border)'
      }}>
        <ActionButton
          icon="📅"
          onClick={() => setActiveView(activeView === 'weekly' ? null : 'weekly')}
          isActive={activeView === 'weekly'}
          extraStyles={{ padding: '12px' }}
        />
        <ActionButton
          icon="🚀"
          onClick={() => setActiveView(activeView === 'goals' ? null : 'goals')}
          isActive={activeView === 'goals'}
          extraStyles={{ padding: '12px' }}
        />
        <ActionButton
          icon="🚧"
          onClick={() => setActiveView(activeView === 'stats' ? null : 'stats')}
          isActive={activeView === 'stats'}
          extraStyles={{ padding: '12px' }}
        />
        <ActionButton
          icon="🎯"
          onClick={() => setActiveView(activeView === 'history' ? null : 'history')}
          isActive={activeView === 'history'}
          extraStyles={{ padding: '12px' }}
        />
      </div>
    </StyledCard>

    {activeView === 'weekly' && (
      <WeeklyGoalsView entries={currentWeekNotes} />
    )}
    {activeView === 'goals' && (
      <GoalsView
        entries={currentMonthNotes}
        daysInMonth={new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate()}
      />
    )}
    {activeView === 'stats' && <TrendsView trends={calculateTrends()} />}
    {activeView === 'history' && (
      <HistoricalView
        sortedNotes={sortedNotes}
        currentPage={currentPage}
        setCurrentPage={setCurrentPage}
        updateHabit={updateHabit}
        getHabitStatus={getHabitStatus}
        getHabitDuration={getHabitDuration}
        editingTime={editingTime}
        setEditingTime={setEditingTime}
        updateHabitDuration={updateHabitDuration}
        calculateCompletedHabits={calculateCompletedHabits}
      />
    )}
  </div>
);
}

return HabitTracker;
Editor is loading...
Leave a Comment