This commit is contained in:
2025-10-13 16:19:17 +02:00
parent b83fe9c5c4
commit 7f6db80195
30 changed files with 12203 additions and 0 deletions

143
src/lib/storage.js Normal file
View File

@@ -0,0 +1,143 @@
const STORAGE_KEY = 'habitgrid_data';
export const getHabits = () => {
try {
const data = localStorage.getItem(STORAGE_KEY);
return data ? JSON.parse(data) : [];
} catch (error) {
console.error('Error loading habits:', error);
return [];
}
};
export const getHabit = (id) => {
const habits = getHabits();
return habits.find(h => h.id === id);
};
export const saveHabit = (habit) => {
const habits = getHabits();
const newHabit = {
...habit,
id: Date.now().toString(),
};
habits.push(newHabit);
localStorage.setItem(STORAGE_KEY, JSON.stringify(habits));
return newHabit;
};
export const updateHabit = (id, updates) => {
const habits = getHabits();
const index = habits.findIndex(h => h.id === id);
if (index !== -1) {
habits[index] = { ...habits[index], ...updates };
localStorage.setItem(STORAGE_KEY, JSON.stringify(habits));
}
};
export const deleteHabit = (id) => {
const habits = getHabits();
const filtered = habits.filter(h => h.id !== id);
localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered));
};
export const toggleCompletion = (habitId, dateStr) => {
const habits = getHabits();
const habit = habits.find(h => h.id === habitId);
if (!habit) return;
const completions = habit.completions || [];
const index = completions.indexOf(dateStr);
if (index > -1) {
completions.splice(index, 1);
} else {
completions.push(dateStr);
}
const { currentStreak, longestStreak } = calculateStreaks(completions);
updateHabit(habitId, {
completions,
currentStreak,
longestStreak: Math.max(longestStreak, habit.longestStreak || 0),
});
};
const calculateStreaks = (completions) => {
if (completions.length === 0) {
return { currentStreak: 0, longestStreak: 0 };
}
const sortedDates = completions
.map(d => new Date(d))
.sort((a, b) => b - a);
let currentStreak = 0;
let longestStreak = 0;
let tempStreak = 1;
const today = new Date();
today.setHours(0, 0, 0, 0);
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
const mostRecent = sortedDates[0];
mostRecent.setHours(0, 0, 0, 0);
if (mostRecent.getTime() === today.getTime() || mostRecent.getTime() === yesterday.getTime()) {
currentStreak = 1;
for (let i = 1; i < sortedDates.length; i++) {
const current = new Date(sortedDates[i]);
current.setHours(0, 0, 0, 0);
const previous = new Date(sortedDates[i - 1]);
previous.setHours(0, 0, 0, 0);
const diffDays = Math.floor((previous - current) / (1000 * 60 * 60 * 24));
if (diffDays === 1) {
currentStreak++;
tempStreak++;
} else {
break;
}
}
}
tempStreak = 1;
for (let i = 1; i < sortedDates.length; i++) {
const current = new Date(sortedDates[i]);
current.setHours(0, 0, 0, 0);
const previous = new Date(sortedDates[i - 1]);
previous.setHours(0, 0, 0, 0);
const diffDays = Math.floor((previous - current) / (1000 * 60 * 60 * 24));
if (diffDays === 1) {
tempStreak++;
longestStreak = Math.max(longestStreak, tempStreak);
} else {
tempStreak = 1;
}
}
longestStreak = Math.max(longestStreak, currentStreak, 1);
return { currentStreak, longestStreak };
};
export const exportData = () => {
const habits = getHabits();
return JSON.stringify(habits, null, 2);
};
export const importData = (jsonString) => {
const habits = JSON.parse(jsonString);
localStorage.setItem(STORAGE_KEY, JSON.stringify(habits));
};
export const clearAllData = () => {
localStorage.removeItem(STORAGE_KEY);
};

42
src/lib/utils-habit.js Normal file
View File

@@ -0,0 +1,42 @@
export const formatDate = (date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
export const isToday = (date) => {
const today = new Date();
return (
date.getDate() === today.getDate() &&
date.getMonth() === today.getMonth() &&
date.getFullYear() === today.getFullYear()
);
};
export const getColorIntensity = (completions, dateStr) => {
const index = completions.indexOf(dateStr);
if (index === -1) return 0;
let streak = 1;
const date = new Date(dateStr);
for (let i = 1; i <= 10; i++) {
const prevDate = new Date(date);
prevDate.setDate(prevDate.getDate() - i);
const prevDateStr = formatDate(prevDate);
if (completions.includes(prevDateStr)) {
streak++;
} else {
break;
}
}
return Math.min(streak / 10, 1);
};
export const getWeekdayLabel = (dayIndex) => {
const labels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
return labels[dayIndex];
};

6
src/lib/utils.js Normal file
View File

@@ -0,0 +1,6 @@
import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs) {
return twMerge(clsx(inputs));
}