mirror of
https://github.com/harivansh-afk/Habit-Tracker.git
synced 2026-04-18 19:03:46 +00:00
working version 1
This commit is contained in:
parent
465ec16e30
commit
33a5f26ec1
4 changed files with 39 additions and 61 deletions
BIN
habits.db
BIN
habits.db
Binary file not shown.
39
src/App.tsx
39
src/App.tsx
|
|
@ -11,8 +11,6 @@ export default function HabitTracker() {
|
||||||
const [activeView, setActiveView] = useState<'habits' | 'calendar' | 'settings'>('habits');
|
const [activeView, setActiveView] = useState<'habits' | 'calendar' | 'settings'>('habits');
|
||||||
const [darkMode, setDarkMode] = useState(() => localStorage.getItem('darkMode') === 'true');
|
const [darkMode, setDarkMode] = useState(() => localStorage.getItem('darkMode') === 'true');
|
||||||
const [currentMonth, setCurrentMonth] = useState(new Date());
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||||
const [streakGoal, setStreakGoal] = useState(7);
|
|
||||||
const [showCompletedHabits, setShowCompletedHabits] = useState(true);
|
|
||||||
|
|
||||||
const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
const daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||||
|
|
||||||
|
|
@ -135,22 +133,6 @@ export default function HabitTracker() {
|
||||||
return habits.filter(habit => habit.completedDates.includes(date));
|
return habits.filter(habit => habit.completedDates.includes(date));
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStreakForHabit = (habit: Habit) => {
|
|
||||||
let streak = 0;
|
|
||||||
const today = new Date();
|
|
||||||
for (let i = 0; i < streakGoal; i++) {
|
|
||||||
const date = new Date(today);
|
|
||||||
date.setDate(today.getDate() - i);
|
|
||||||
const dateString = date.toISOString().split('T')[0];
|
|
||||||
if (habit.completedDates.includes(dateString)) {
|
|
||||||
streak++;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return streak;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleUpdateStreak = async (id: number, newStreak: number) => {
|
const handleUpdateStreak = async (id: number, newStreak: number) => {
|
||||||
// Prevent negative streaks
|
// Prevent negative streaks
|
||||||
if (newStreak < 0) return;
|
if (newStreak < 0) return;
|
||||||
|
|
@ -294,27 +276,6 @@ export default function HabitTracker() {
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="dark:text-white">Streak Goal</span>
|
|
||||||
<select
|
|
||||||
value={streakGoal}
|
|
||||||
onChange={(e) => setStreakGoal(Number(e.target.value))}
|
|
||||||
className="px-3 py-2 border rounded-lg dark:bg-gray-800 dark:border-gray-700 dark:text-white"
|
|
||||||
>
|
|
||||||
{[3, 5, 7, 10, 14, 21, 30].map(days => (
|
|
||||||
<option key={days} value={days}>{days} days</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<span className="dark:text-white">Show Completed Habits</span>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={showCompletedHabits}
|
|
||||||
onChange={(e) => setShowCompletedHabits(e.target.checked)}
|
|
||||||
className="w-4 h-4 rounded border-gray-300 dark:border-gray-600"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Trash2, ChevronUp, ChevronDown } from 'lucide-react';
|
import { Trash2 } from 'lucide-react';
|
||||||
import { Habit } from '../types';
|
import { Habit } from '../types';
|
||||||
|
|
||||||
interface HabitListProps {
|
interface HabitListProps {
|
||||||
|
|
@ -9,9 +9,37 @@ interface HabitListProps {
|
||||||
onToggleHabit: (id: number, date: string) => void;
|
onToggleHabit: (id: number, date: string) => void;
|
||||||
onUpdateHabit: (id: number, name: string) => void;
|
onUpdateHabit: (id: number, name: string) => void;
|
||||||
onDeleteHabit: (id: number) => void;
|
onDeleteHabit: (id: number) => void;
|
||||||
onUpdateStreak: (id: number, streak: number) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const calculateStreak = (completedDates: string[]): number => {
|
||||||
|
if (completedDates.length === 0) return 0;
|
||||||
|
|
||||||
|
// Sort dates in ascending order
|
||||||
|
const sortedDates = [...completedDates].sort((a, b) =>
|
||||||
|
new Date(a).getTime() - new Date(b).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
let currentStreak = 1;
|
||||||
|
let maxStreak = 1;
|
||||||
|
|
||||||
|
// Go through the dates and count consecutive completions
|
||||||
|
for (let i = 1; i < sortedDates.length; i++) {
|
||||||
|
const prevDate = new Date(sortedDates[i - 1]);
|
||||||
|
const currDate = new Date(sortedDates[i]);
|
||||||
|
|
||||||
|
// If dates are consecutive or same day, increment streak
|
||||||
|
if (currDate.getTime() - prevDate.getTime() <= 24 * 60 * 60 * 1000) {
|
||||||
|
currentStreak++;
|
||||||
|
maxStreak = Math.max(maxStreak, currentStreak);
|
||||||
|
} else {
|
||||||
|
// Reset streak counter when there's a gap
|
||||||
|
currentStreak = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxStreak;
|
||||||
|
};
|
||||||
|
|
||||||
export function HabitList({
|
export function HabitList({
|
||||||
habits,
|
habits,
|
||||||
currentWeek,
|
currentWeek,
|
||||||
|
|
@ -19,7 +47,6 @@ export function HabitList({
|
||||||
onToggleHabit,
|
onToggleHabit,
|
||||||
onUpdateHabit,
|
onUpdateHabit,
|
||||||
onDeleteHabit,
|
onDeleteHabit,
|
||||||
onUpdateStreak
|
|
||||||
}: HabitListProps) {
|
}: HabitListProps) {
|
||||||
return (
|
return (
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
|
|
@ -34,7 +61,12 @@ export function HabitList({
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
<th className="px-4 py-2 text-center dark:text-white">Manual Streak</th>
|
<th className="px-4 py-2 text-center dark:text-white">
|
||||||
|
Best Streak
|
||||||
|
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
consecutive completions
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
<th className="px-4 py-2 text-center dark:text-white">Actions</th>
|
<th className="px-4 py-2 text-center dark:text-white">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
@ -60,23 +92,9 @@ export function HabitList({
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
<td className="px-4 py-2 text-center">
|
<td className="px-4 py-2 text-center">
|
||||||
<div className="flex items-center justify-center space-x-2">
|
<span className="dark:text-white font-medium">
|
||||||
<button
|
{calculateStreak(habit.completedDates)}
|
||||||
onClick={() => onUpdateStreak(habit.id, (habit.manualStreak || 0) - 1)}
|
</span>
|
||||||
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full"
|
|
||||||
>
|
|
||||||
<ChevronDown className="h-4 w-4 dark:text-white" />
|
|
||||||
</button>
|
|
||||||
<span className="dark:text-white min-w-[2rem]">
|
|
||||||
{habit.manualStreak || 0}
|
|
||||||
</span>
|
|
||||||
<button
|
|
||||||
onClick={() => onUpdateStreak(habit.id, (habit.manualStreak || 0) + 1)}
|
|
||||||
className="p-1 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full"
|
|
||||||
>
|
|
||||||
<ChevronUp className="h-4 w-4 dark:text-white" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-center">
|
<td className="px-4 py-2 text-center">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,4 @@ export interface Habit {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
completedDates: string[];
|
completedDates: string[];
|
||||||
manualStreak: number;
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue