mirror of
https://github.com/harivansh-afk/Habit-Tracker.git
synced 2026-04-15 07:04:47 +00:00
working version 2
This commit is contained in:
parent
33a5f26ec1
commit
d412046627
4 changed files with 75 additions and 26 deletions
BIN
habits.db
BIN
habits.db
Binary file not shown.
22
src/App.tsx
22
src/App.tsx
|
|
@ -137,15 +137,21 @@ export default function HabitTracker() {
|
||||||
// Prevent negative streaks
|
// Prevent negative streaks
|
||||||
if (newStreak < 0) return;
|
if (newStreak < 0) return;
|
||||||
|
|
||||||
// Update in database
|
try {
|
||||||
await db.habits.update(id, { manualStreak: newStreak });
|
// Update in database
|
||||||
|
await fetch(`http://localhost:5000/api/habits/${id}/streak`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ streak: newStreak }),
|
||||||
|
});
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
setHabits(habits.map(habit =>
|
setHabits(habits.map(habit =>
|
||||||
habit.id === id
|
habit.id === id ? { ...habit, manualStreak: newStreak } : habit
|
||||||
? { ...habit, manualStreak: newStreak }
|
));
|
||||||
: habit
|
} catch (error) {
|
||||||
));
|
console.error('Error updating streak:', error);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -9,35 +9,60 @@ 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, newStreak: number) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const calculateStreak = (completedDates: string[]): number => {
|
const calculateStreak = (completedDates: string[]): { currentStreak: number; bestStreak: number } => {
|
||||||
if (completedDates.length === 0) return 0;
|
if (completedDates.length === 0) return { currentStreak: 0, bestStreak: 0 };
|
||||||
|
|
||||||
// Sort dates in ascending order
|
|
||||||
const sortedDates = [...completedDates].sort((a, b) =>
|
const sortedDates = [...completedDates].sort((a, b) =>
|
||||||
new Date(a).getTime() - new Date(b).getTime()
|
new Date(a).getTime() - new Date(b).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
let currentStreak = 1;
|
let currentStreak = 1;
|
||||||
let maxStreak = 1;
|
let bestStreak = 1;
|
||||||
|
let tempStreak = 1;
|
||||||
|
|
||||||
// Go through the dates and count consecutive completions
|
// Check if the last completion was today or yesterday
|
||||||
|
const lastDate = new Date(sortedDates[sortedDates.length - 1]);
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
lastDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
const diffDays = Math.floor((today.getTime() - lastDate.getTime()) / (24 * 60 * 60 * 1000));
|
||||||
|
|
||||||
|
// If the last completion was more than a day ago, current streak is 0
|
||||||
|
if (diffDays > 1) {
|
||||||
|
currentStreak = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate streaks
|
||||||
for (let i = 1; i < sortedDates.length; i++) {
|
for (let i = 1; i < sortedDates.length; i++) {
|
||||||
const prevDate = new Date(sortedDates[i - 1]);
|
const prevDate = new Date(sortedDates[i - 1]);
|
||||||
const currDate = new Date(sortedDates[i]);
|
const currDate = new Date(sortedDates[i]);
|
||||||
|
prevDate.setHours(0, 0, 0, 0);
|
||||||
|
currDate.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
// If dates are consecutive or same day, increment streak
|
const diffTime = currDate.getTime() - prevDate.getTime();
|
||||||
if (currDate.getTime() - prevDate.getTime() <= 24 * 60 * 60 * 1000) {
|
const diffDays = Math.floor(diffTime / (24 * 60 * 60 * 1000));
|
||||||
currentStreak++;
|
|
||||||
maxStreak = Math.max(maxStreak, currentStreak);
|
if (diffDays === 1) {
|
||||||
|
tempStreak++;
|
||||||
|
bestStreak = Math.max(bestStreak, tempStreak);
|
||||||
|
} else if (diffDays === 0) {
|
||||||
|
// Same day completions don't affect streak
|
||||||
|
continue;
|
||||||
} else {
|
} else {
|
||||||
// Reset streak counter when there's a gap
|
tempStreak = 1;
|
||||||
currentStreak = 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return maxStreak;
|
// Current streak should be the same as tempStreak if the last completion was today or yesterday
|
||||||
|
if (diffDays <= 1) {
|
||||||
|
currentStreak = tempStreak;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { currentStreak, bestStreak };
|
||||||
};
|
};
|
||||||
|
|
||||||
export function HabitList({
|
export function HabitList({
|
||||||
|
|
@ -47,6 +72,7 @@ export function HabitList({
|
||||||
onToggleHabit,
|
onToggleHabit,
|
||||||
onUpdateHabit,
|
onUpdateHabit,
|
||||||
onDeleteHabit,
|
onDeleteHabit,
|
||||||
|
onUpdateStreak,
|
||||||
}: HabitListProps) {
|
}: HabitListProps) {
|
||||||
return (
|
return (
|
||||||
<table className="w-full">
|
<table className="w-full">
|
||||||
|
|
@ -61,11 +87,11 @@ export function HabitList({
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
<th className="px-4 py-2 text-center dark:text-white">
|
||||||
|
Current Streak
|
||||||
|
</th>
|
||||||
<th className="px-4 py-2 text-center dark:text-white">
|
<th className="px-4 py-2 text-center dark:text-white">
|
||||||
Best Streak
|
Best Streak
|
||||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
||||||
consecutive completions
|
|
||||||
</div>
|
|
||||||
</th>
|
</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>
|
||||||
|
|
@ -78,6 +104,8 @@ export function HabitList({
|
||||||
type="text"
|
type="text"
|
||||||
value={habit.name}
|
value={habit.name}
|
||||||
onChange={(e) => onUpdateHabit(habit.id, e.target.value)}
|
onChange={(e) => onUpdateHabit(habit.id, e.target.value)}
|
||||||
|
aria-label="Habit name"
|
||||||
|
placeholder="Enter habit name"
|
||||||
className="bg-transparent border-none focus:outline-none focus:ring-2 focus:ring-gray-300 rounded px-2"
|
className="bg-transparent border-none focus:outline-none focus:ring-2 focus:ring-gray-300 rounded px-2"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
|
|
@ -86,14 +114,28 @@ export function HabitList({
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={habit.completedDates.includes(date)}
|
checked={habit.completedDates.includes(date)}
|
||||||
onChange={() => onToggleHabit(habit.id, date)}
|
onChange={() => {
|
||||||
|
onToggleHabit(habit.id, date);
|
||||||
|
const newCompletedDates = habit.completedDates.includes(date)
|
||||||
|
? habit.completedDates.filter(d => d !== date)
|
||||||
|
: [...habit.completedDates, date];
|
||||||
|
|
||||||
|
const { currentStreak, bestStreak } = calculateStreak(newCompletedDates);
|
||||||
|
onUpdateStreak(habit.id, bestStreak);
|
||||||
|
}}
|
||||||
|
aria-label={`Mark ${habit.name} as completed for ${date}`}
|
||||||
className="w-4 h-4 rounded border-gray-300 dark:border-gray-600"
|
className="w-4 h-4 rounded border-gray-300 dark:border-gray-600"
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
<td className="px-4 py-2 text-center">
|
<td className="px-4 py-2 text-center">
|
||||||
<span className="dark:text-white font-medium">
|
<span className="dark:text-white font-medium">
|
||||||
{calculateStreak(habit.completedDates)}
|
{calculateStreak(habit.completedDates).currentStreak}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="px-4 py-2 text-center">
|
||||||
|
<span className="dark:text-white font-medium">
|
||||||
|
{calculateStreak(habit.completedDates).bestStreak}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-2 text-center">
|
<td className="px-4 py-2 text-center">
|
||||||
|
|
@ -109,4 +151,4 @@ export function HabitList({
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,5 @@ export interface Habit {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
completedDates: string[];
|
completedDates: string[];
|
||||||
|
bestStreak: number;
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue