Categories are shared across Tasks, Habits, and Finance.
Categories are shared across Tasks and Finance.
@@ -749,7 +614,7 @@
Data Version
About
-
Task Manager - Your complete task and habit management system.
+
Task Manager - Your complete task management system.
All data is stored locally in your browser.
diff --git a/manifest.json b/manifest.json
index 561fcd5..366300d 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"name": "Task Manager",
"short_name": "TaskManager",
- "description": "Complete task and habit management system with gamification",
+ "description": "Complete task management system",
"start_url": "./index.html",
"display": "standalone",
"background_color": "#ffffff",
diff --git a/package.json b/package.json
index 7a3a250..e0b2688 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "taskmanagerweb",
"version": "1.0.0",
- "description": "A comprehensive Progressive Web App (PWA) for managing tasks, projects, habits, and finances with gamification features.",
+ "description": "A comprehensive Progressive Web App (PWA) for managing tasks, projects, and finances.",
"main": "service-worker.js",
"scripts": {
"build": "tsc",
diff --git a/src/app.ts b/src/app.ts
index 8391d25..993dc79 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -2,7 +2,7 @@
// Main Application Logic
// ========================
-import { StorageManager, storage, STORAGE_VERSION, Task, Habit, FinanceItem, WishItem, WishList, Note, ShoppingItem, getDaysUntilDueText } from './storage.js';
+import { StorageManager, storage, STORAGE_VERSION, Task, FinanceItem, WishItem, WishList, Note, ShoppingItem, getDaysUntilDueText } from './storage.js';
const FILTER_SETTINGS_KEY = 'taskManagerFilterSettings';
@@ -20,7 +20,6 @@ interface FilteredFinanceItem extends FinanceItem {
class TaskManager {
currentEditingTaskId: string | null = null;
currentEditingProjectId: string | null = null;
- currentEditingHabitId: string | null = null;
currentEditingFinanceId: string | null = null;
currentEditingFinanceType: string | null = null;
currentEditingWishItemId: string | null = null;
@@ -32,28 +31,6 @@ class TaskManager {
selectedDate: Date = new Date();
tasksExpanded: boolean = false;
hideCompleted: boolean = false;
- emojis: string[] = [
- // Activity
- '๐ช', '๐', '๐ด', '๐', '๐ง', '๐', '๐บ', 'โน๏ธ',
- // Food & Health
- '๐ฅ', '๐', '๐ฅ', '๐', '๐ฅ', '๐ง', '๐ฅค', 'โ',
- // Work & Productivity
- '๐', 'โ๏ธ', '๐ผ', '๐ฏ', '๐', '๐ป', '๐ฑ', 'โจ๏ธ',
- // Learning & Mind
- '๐ง ', '๐', '๐', '๐ก', '๐ฌ', '๐จ', '๐ต', '๐ญ',
- // Nature & Outdoors
- '๐ฟ', '๐ณ', '๐', '๐', '๐', 'โฐ๏ธ', '๐๏ธ', '๐ฆ',
- // Sleep & Rest
- '๐ด', '๐๏ธ', '๐', '๐ฏ๏ธ', '๐', '๐ค', '๐ง', '๐',
- // Social & Fun
- '๐จโ๐ฉโ๐งโ๐ฆ', '๐ค', '๐', '๐', 'โค๏ธ', '๐ค', '๐', '๐',
- // Sports & Games
- 'โฝ', '๐', '๐พ', '๐', '๐ฏ', 'โ๏ธ', '๐ฒ', '๐',
- // Habits & Goals
- 'โญ', '๐ฏ', '๐', '๐ฅ', '๐ฅ', '๐', 'โจ', '๐',
- // More Emojis
- '๐', '๐', '๐', '๐
', 'โฐ', '๐ฐ', '๐ช', '๐ข'
- ];
constructor() {
this.init();
@@ -97,17 +74,6 @@ class TaskManager {
document.getElementById('closeProjectDetailBtn')!.addEventListener('click', () => this.closeProjectDetailModal());
document.getElementById('editProjectFromDetailBtn')!.addEventListener('click', () => this.editProjectFromDetail());
- // Habits section
- document.getElementById('addHabitBtn')!.addEventListener('click', () => this.openHabitModal());
- document.getElementById('habitForm')!.addEventListener('submit', (e) => this.saveHabit(e));
- document.getElementById('cancelHabitBtn')!.addEventListener('click', () => this.closeHabitModal());
- document.getElementById('deleteHabitBtn')!.addEventListener('click', () => this.deleteHabit());
- document.getElementById('habitEmojiBtn')!.addEventListener('click', () => this.openEmojiPicker());
- document.getElementById('closeEmojiBtn')!.addEventListener('click', () => this.closeEmojiPicker());
- document.getElementById('habitCategory')!.addEventListener('change', (e) => this.handleCategoryChange('habit', (e.target as HTMLSelectElement).value));
- document.getElementById('habitCategorySave')!.addEventListener('click', () => this.handleAddCategory('habit'));
- document.getElementById('habitCategoryCancel')!.addEventListener('click', () => this.cancelAddCategory('habit'));
-
// Finances section
document.getElementById('addExpenseBtn')!.addEventListener('click', () => this.openFinanceModal('expense'));
document.getElementById('addRevenueBtn')!.addEventListener('click', () => this.openFinanceModal('revenue'));
@@ -159,7 +125,6 @@ class TaskManager {
});
// Settings
- document.getElementById('saveTasksPerLevel')!.addEventListener('click', () => this.saveTasksPerLevel());
document.getElementById('exportBtn')!.addEventListener('click', () => this.exportData());
document.getElementById('importBtn')!.addEventListener('click', () => document.getElementById('importFile')!.click());
document.getElementById('importFile')!.addEventListener('change', (e) => this.importData(e));
@@ -259,8 +224,6 @@ class TaskManager {
this.renderTasks();
} else if (tabName === 'projects') {
this.renderProjects();
- } else if (tabName === 'habits') {
- this.renderHabits();
} else if (tabName === 'finances') {
this.renderFinances();
} else if (tabName === 'wishlist') {
@@ -293,69 +256,15 @@ class TaskManager {
renderDashboard(): void {
const today = this.getSelectedDateStr();
const tasks = storage.getTasks();
- const habits = storage.getHabits();
- const userStats = storage.getUserStats();
-
- // Update header stats
- document.getElementById('userLevel')!.textContent = String(userStats.level);
- document.getElementById('dailyStreak')!.textContent = String(userStats.dailyStreak);
-
- // Update level progress
- const settings = storage.getSettings();
- const completedTasksCount = tasks.filter(t => t.completed).length;
- const tasksInCurrentLevel = completedTasksCount % settings.tasksPerLevel;
- const tasksNeeded = settings.tasksPerLevel;
- document.getElementById('levelProgress')!.textContent = `${tasksInCurrentLevel}/${tasksNeeded} tasks`;
// Selected day's overview
const todayTasks = tasks.filter(t => t.dueDate === today && !t.completed);
const overdueTasks = tasks.filter(t => !t.completed && !!t.dueDate && t.dueDate < today);
const completedToday = tasks.filter(t => t.completedDate === today);
- const todayDay = this.selectedDate.getDay();
-
- // Find incomplete habits for selected day
- const incompleteHabits = habits.filter(habit => {
- const isValidDay = !habit.daysOfWeek || habit.daysOfWeek.includes(todayDay);
- if (!isValidDay) return false;
- const todaysCompletions = storage.countHabitCompletionsForDate(habit.id, today);
- const targetGoal = habit.targetGoal || 1;
- return todaysCompletions < targetGoal;
- });
document.getElementById('todayTasksCount')!.textContent = String(todayTasks.length);
document.getElementById('overdueTasksCount')!.textContent = String(overdueTasks.length);
document.getElementById('completedTodayCount')!.textContent = String(completedToday.length);
- document.getElementById('incompleteHabitsCount')!.textContent = String(incompleteHabits.length);
-
- // Render incomplete habits list
- const incompleteHabitsList = document.getElementById('incompleteHabitsList')!;
- if (incompleteHabits.length === 0) {
- incompleteHabitsList.innerHTML = '
All habits completed for today! ๐
';
- } else {
- incompleteHabitsList.innerHTML = incompleteHabits.map(habit => {
- const todaysCompletions = storage.countHabitCompletionsForDate(habit.id, today);
- const targetGoal = habit.targetGoal || 1;
- const percentage = Math.min(100, Math.round((todaysCompletions / targetGoal) * 100));
- return `
-
-
-
- ${habit.icon}
- ${habit.name}
-
-
${todaysCompletions}/${targetGoal} (${percentage}%)
-
-
- `;
- }).join('');
-
- // Add click handlers to navigate to habits
- document.querySelectorAll('.habit-item').forEach(item => {
- item.addEventListener('click', () => {
- this.switchTab('habits');
- });
- });
- }
// Recent activity
this.renderRecentActivity();
@@ -378,21 +287,6 @@ class TaskManager {
});
});
- // Collect activities from habits
- if (data.dailyHabitLogs) {
- data.dailyHabitLogs.slice(-10).forEach(log => {
- const habit = data.habits.find(h => h.id === log.habitId);
- if (habit) {
- activities.push({
- type: 'habit',
- message: `Completed habit: ${habit.name}`,
- date: log.date,
- icon: 'โญ'
- });
- }
- });
- }
-
// Sort by date
activities.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
@@ -784,17 +678,14 @@ class TaskManager {
task.completed = !task.completed;
if (task.completed) {
task.completedDate = this.getSelectedDateStr();
- storage.updateDailyStreak(true);
// If repeatable, immediately create next task with recalculated due date
if (task.repeatType !== 'none') {
this.createNextRecurringTask(task);
}
} else {
- // If uncompleting, also recalculate level
task.completedDate = null;
}
storage.updateTask(taskId, task);
- storage.updateLevel();
this.renderTasks();
this.renderDashboard();
this.renderProjects();
@@ -1063,216 +954,6 @@ class TaskManager {
});
}
- // ========================
- // Habits Management
- // ========================
- renderHabits(): void {
- const habits = storage.getHabits();
- const container = document.getElementById('habitsList')!;
-
- if (habits.length === 0) {
- container.innerHTML = '
No habits yet. Create daily habits to build streaks!
';
- return;
- }
-
- container.innerHTML = habits.map(habit => this.renderHabitCard(habit)).join('');
-
- document.querySelectorAll('.habit-card').forEach(card => {
- card.addEventListener('click', (e) => {
- if (!(e.target as HTMLElement).classList.contains('habit-checkbox')) {
- this.openHabitModal((card as HTMLElement).dataset.habitId!);
- }
- });
- });
-
- document.querySelectorAll('.habit-checkbox').forEach(btn => {
- btn.addEventListener('click', (e) => {
- e.stopPropagation();
- const habitId = (e.target as HTMLElement).dataset.habitId!;
- this.completeHabit(habitId);
- });
- });
- }
-
- renderHabitCard(habit: Habit): string {
- const selectedDayOfWeek = this.selectedDate.getDay();
- const isValidDay = !habit.daysOfWeek || habit.daysOfWeek.includes(selectedDayOfWeek);
- const selectedDateStr = this.getSelectedDateStr();
- const isPastDay = !this.isSelectedDateToday();
-
- const todaysCompletions = storage.countHabitCompletionsForDate(habit.id, selectedDateStr);
- const targetGoal = habit.targetGoal || 1;
- const percentage = Math.min(100, Math.round((todaysCompletions / targetGoal) * 100));
- const isComplete = todaysCompletions >= targetGoal;
-
- const btnLabel = !isValidDay
- ? 'โ Not Scheduled'
- : isComplete
- ? (isPastDay ? 'โ Logged' : 'โ Done for Today')
- : (isPastDay ? '+ Log Past Day' : '+ Complete');
-
- return `
-
-
${habit.icon}
-
${habit.name}
- ${habit.description ? `
${habit.description}
` : ''}
-
-
- Streak
- ${habit.streak || 0}
-
-
- Progress
- ${todaysCompletions}/${targetGoal}
-
-
-
-
-
- ${isComplete ? 'โ Complete!' : `${percentage}% Complete`}
-
-
-
-
- `;
- }
-
- openHabitModal(habitId: string | null = null): void {
- this.currentEditingHabitId = habitId;
- const modal = document.getElementById('habitModal')!;
- const form = document.getElementById('habitForm') as HTMLFormElement;
- const deleteBtn = document.getElementById('deleteHabitBtn') as HTMLElement;
-
- form.reset();
- deleteBtn.style.display = 'none';
- (document.getElementById('habitIcon') as HTMLInputElement).value = 'โญ';
- document.getElementById('habitIconDisplay')!.textContent = 'โญ';
-
- document.querySelectorAll
('input[name="habitDay"]').forEach(checkbox => {
- checkbox.checked = false;
- });
-
- if (habitId) {
- const habit = storage.getHabits().find(h => h.id === habitId);
- if (habit) {
- (document.getElementById('habitName') as HTMLInputElement).value = habit.name;
- (document.getElementById('habitDescription') as HTMLTextAreaElement).value = habit.description || '';
- (document.getElementById('habitIcon') as HTMLInputElement).value = habit.icon || 'โญ';
- document.getElementById('habitIconDisplay')!.textContent = habit.icon || 'โญ';
- (document.getElementById('habitCategory') as HTMLSelectElement).value = habit.category || '';
- (document.getElementById('habitTargetGoal') as HTMLInputElement).value = String(habit.targetGoal || 1);
- deleteBtn.style.display = 'block';
-
- if (habit.daysOfWeek && Array.isArray(habit.daysOfWeek)) {
- habit.daysOfWeek.forEach(day => {
- const checkbox = document.querySelector(`input[name="habitDay"][value="${day}"]`);
- if (checkbox) {
- checkbox.checked = true;
- }
- });
- } else {
- document.querySelectorAll('input[name="habitDay"]').forEach(checkbox => {
- checkbox.checked = true;
- });
- }
- }
- } else {
- document.querySelectorAll('input[name="habitDay"]').forEach(checkbox => {
- checkbox.checked = true;
- });
- }
-
- this.loadCategoryDropdown('habit');
- modal.classList.add('active');
- }
-
- closeHabitModal(): void {
- document.getElementById('habitModal')!.classList.remove('active');
- this.currentEditingHabitId = null;
- }
-
- saveHabit(e: Event): void {
- e.preventDefault();
-
- const selectedDays = Array.from(document.querySelectorAll('input[name="habitDay"]:checked'))
- .map(checkbox => parseInt(checkbox.value));
-
- const habit: Partial = {
- name: (document.getElementById('habitName') as HTMLInputElement).value,
- description: (document.getElementById('habitDescription') as HTMLTextAreaElement).value,
- icon: (document.getElementById('habitIcon') as HTMLInputElement).value,
- category: (document.getElementById('habitCategory') as HTMLSelectElement).value || null,
- targetGoal: parseInt((document.getElementById('habitTargetGoal') as HTMLInputElement).value) || 1,
- daysOfWeek: selectedDays.length > 0 ? selectedDays : [0, 1, 2, 3, 4, 5, 6]
- };
-
- if (this.currentEditingHabitId) {
- storage.updateHabit(this.currentEditingHabitId, habit);
- } else {
- storage.addHabit(habit);
- }
-
- this.closeHabitModal();
- this.renderHabits();
- }
-
- deleteHabit(): void {
- if (this.currentEditingHabitId) {
- if (confirm('Are you sure you want to delete this habit?')) {
- storage.deleteHabit(this.currentEditingHabitId);
- this.closeHabitModal();
- this.renderHabits();
- }
- }
- }
-
- completeHabit(habitId: string): void {
- const habit = storage.getHabits().find(h => h.id === habitId);
- if (habit) {
- const selectedDayOfWeek = this.selectedDate.getDay();
- const isValidDay = !habit.daysOfWeek || habit.daysOfWeek.includes(selectedDayOfWeek);
-
- if (isValidDay) {
- storage.logHabitCompletion(habitId, this.selectedDate);
- storage.updateDailyStreak(true);
- this.renderHabits();
- this.renderDashboard();
- }
- }
- }
-
- openEmojiPicker(): void {
- const modal = document.getElementById('emojiModal')!;
- const emojiGrid = document.getElementById('emojiGrid')!;
-
- emojiGrid.innerHTML = this.emojis.map(emoji =>
- ``
- ).join('');
-
- document.querySelectorAll('.emoji-btn').forEach(btn => {
- btn.addEventListener('click', (e) => {
- e.preventDefault();
- this.selectEmoji((e.target as HTMLElement).dataset.emoji!);
- });
- });
-
- modal.classList.add('active');
- }
-
- closeEmojiPicker(): void {
- document.getElementById('emojiModal')!.classList.remove('active');
- }
-
- selectEmoji(emoji: string): void {
- (document.getElementById('habitIcon') as HTMLInputElement).value = emoji;
- document.getElementById('habitIconDisplay')!.textContent = emoji;
- this.closeEmojiPicker();
- }
-
// ========================
// Category Management
// ========================
@@ -2431,50 +2112,9 @@ class TaskManager {
// Settings
// ========================
renderSettings(): void {
- this.loadSettings();
- this.updateSettingsStatus();
this.renderCategoryManagement();
}
- loadSettings(): void {
- const settings = storage.getSettings();
- const tasksPerLevelInput = document.getElementById('tasksPerLevel') as HTMLInputElement | null;
- if (tasksPerLevelInput) {
- tasksPerLevelInput.value = String(settings.tasksPerLevel || 30);
- }
- }
-
- updateSettingsStatus(): void {
- const settings = storage.getSettings();
- const tasks = storage.getTasks();
- const completedTasksCount = tasks.filter(t => t.completed).length;
- const userStats = storage.getUserStats();
- const tasksInCurrentLevel = completedTasksCount % settings.tasksPerLevel;
- const tasksToNext = settings.tasksPerLevel - tasksInCurrentLevel;
-
- const currentLevelEl = document.getElementById('settingsCurrentLevel');
- const totalCompletedEl = document.getElementById('settingsTotalCompleted');
- const tasksToNextEl = document.getElementById('settingsTasksToNext');
-
- if (currentLevelEl) currentLevelEl.textContent = String(userStats.level);
- if (totalCompletedEl) totalCompletedEl.textContent = String(completedTasksCount);
- if (tasksToNextEl) tasksToNextEl.textContent = String(tasksToNext);
- }
-
- saveTasksPerLevel(): void {
- const tasksPerLevel = parseInt((document.getElementById('tasksPerLevel') as HTMLInputElement).value);
-
- if (isNaN(tasksPerLevel) || tasksPerLevel < 1) {
- alert('Please enter a valid number (minimum 1)');
- return;
- }
-
- storage.updateSettings({ tasksPerLevel });
- alert('Settings saved! Level has been recalculated.');
- this.updateSettingsStatus();
- this.renderDashboard();
- }
-
exportData(): void {
const data = storage.exportData();
const blob = new Blob([data], { type: 'application/json' });
diff --git a/src/storage.ts b/src/storage.ts
index 27df0dc..a7da72d 100644
--- a/src/storage.ts
+++ b/src/storage.ts
@@ -34,26 +34,6 @@ export interface Project {
createdDate: string;
}
-export interface Habit {
- id: string;
- name: string;
- description?: string;
- icon: string;
- category?: string | null;
- targetGoal: number;
- daysOfWeek?: number[];
- streak: number;
- lastCompletedDate?: string | null;
- createdDate: string;
-}
-
-export interface HabitLog {
- id: string;
- habitId: string;
- date: string;
- timestamp: string;
-}
-
export interface FinanceItem {
id: string;
description: string;
@@ -98,35 +78,16 @@ export interface ShoppingItem {
createdDate: string;
}
-export interface UserStats {
- level: number;
- dailyStreak: number;
- lastActivityDate: string | null;
-}
-
-interface LegacyUserStats extends UserStats {
- totalPoints?: number;
- pointsBreakdown?: Record;
-}
-
-export interface Settings {
- tasksPerLevel: number;
-}
-
export interface AppData {
version: string;
schemaVersion: number;
lastUpdated: string;
tasks: Task[];
projects: Project[];
- habits: Habit[];
- dailyHabitLogs: HabitLog[];
expenses: FinanceItem[];
revenue: FinanceItem[];
charges: FinanceItem[];
categories: string[];
- userStats: UserStats;
- settings: Settings;
wishList: WishItem[];
wishLists: WishList[];
notes: Note[];
@@ -169,20 +130,10 @@ export class StorageManager {
lastUpdated: new Date().toISOString(),
tasks: [],
projects: [],
- habits: [],
- dailyHabitLogs: [],
expenses: [],
revenue: [],
charges: [],
categories: ['Work', 'Personal', 'Home', 'Shopping', 'Health', 'Fitness', 'Learning', 'Productivity', 'Food', 'Transportation', 'Entertainment', 'Utilities', 'Income'],
- userStats: {
- level: 1,
- dailyStreak: 0,
- lastActivityDate: null
- },
- settings: {
- tasksPerLevel: 30
- },
wishList: [],
wishLists: [],
notes: [],
@@ -286,121 +237,6 @@ export class StorageManager {
return data.projects || [];
}
- // Habit Management
- addHabit(habit: Partial): Habit {
- const data = this.getData();
- const newHabit: Habit = {
- ...habit,
- id: this.generateId(),
- createdDate: new Date().toISOString(),
- streak: 0,
- lastCompletedDate: null,
- targetGoal: habit.targetGoal || 1,
- name: habit.name || '',
- icon: habit.icon || 'โญ',
- } as Habit;
- data.habits.push(newHabit);
- this.saveData(data);
- return newHabit;
- }
-
- updateHabit(habitId: string, updates: Partial): Habit | undefined {
- const data = this.getData();
- const habit = data.habits.find(h => h.id === habitId);
- if (habit) {
- Object.assign(habit, updates);
- if (!habit.targetGoal) habit.targetGoal = 1;
- this.saveData(data);
- }
- return habit;
- }
-
- deleteHabit(habitId: string): void {
- const data = this.getData();
- data.habits = data.habits.filter(h => h.id !== habitId);
- this.saveData(data);
- }
-
- getHabits(): Habit[] {
- const data = this.getData();
- return data.habits || [];
- }
-
- logHabitCompletion(habitId: string, date: Date = new Date()): void {
- const data = this.getData();
- const dateStr = this.formatDate(date);
-
- data.dailyHabitLogs.push({
- id: this.generateId(),
- habitId,
- date: dateStr,
- timestamp: new Date().toISOString()
- });
-
- // Update habit streak based on fully-completed consecutive days
- const habit = data.habits.find(h => h.id === habitId);
- if (habit) {
- habit.lastCompletedDate = dateStr;
- habit.streak = this.calculateHabitStreak(habitId, habit.targetGoal, data.dailyHabitLogs);
- }
-
- this.saveData(data);
- }
-
- calculateHabitStreak(habitId: string, targetGoal: number, logs: HabitLog[]): number {
- // Count completions per date for this habit
- const habitLogs = logs.filter(l => l.habitId === habitId);
- const countsByDate: Record = {};
- for (const log of habitLogs) {
- countsByDate[log.date] = (countsByDate[log.date] || 0) + 1;
- }
-
- // Get dates where fully completed (>= targetGoal), sorted most recent first
- const completedDates = Object.keys(countsByDate)
- .filter(date => countsByDate[date] >= targetGoal)
- .sort()
- .reverse();
-
- if (completedDates.length === 0) return 0;
-
- // Count consecutive days going backward from the most recent fully-completed day
- let streak = 1;
- let currentDate = completedDates[0];
-
- for (let i = 1; i < completedDates.length; i++) {
- const [year, month, day] = currentDate.split('-').map(Number);
- const prevDay = new Date(year, month - 1, day);
- prevDay.setDate(prevDay.getDate() - 1);
- const expectedDate = this.formatDate(prevDay);
-
- if (completedDates[i] === expectedDate) {
- streak++;
- currentDate = completedDates[i];
- } else {
- break;
- }
- }
-
- return streak;
- }
-
- isHabitCompletedToday(habitId: string): boolean {
- const data = this.getData();
- const todayStr = this.formatDate(new Date());
- return data.dailyHabitLogs.some(log => log.habitId === habitId && log.date === todayStr);
- }
-
- countHabitCompletionsToday(habitId: string): number {
- const data = this.getData();
- const todayStr = this.formatDate(new Date());
- return data.dailyHabitLogs.filter(log => log.habitId === habitId && log.date === todayStr).length;
- }
-
- countHabitCompletionsForDate(habitId: string, dateStr: string): number {
- const data = this.getData();
- return data.dailyHabitLogs.filter(log => log.habitId === habitId && log.date === dateStr).length;
- }
-
// Finance Management
addExpense(expense: Partial): FinanceItem {
const data = this.getData();
@@ -717,39 +553,6 @@ export class StorageManager {
this.saveData(data);
}
- updateLevel(): void {
- const data = this.getData();
- const settings = this.getSettings();
- const completedTasksCount = data.tasks.filter(t => t.completed).length;
- data.userStats.level = Math.floor(completedTasksCount / settings.tasksPerLevel) + 1;
- this.saveData(data);
- }
-
- updateDailyStreak(increment: boolean = true): void {
- const data = this.getData();
- const today = this.formatDate(new Date());
-
- if (increment) {
- if (data.userStats.lastActivityDate !== today) {
- const yesterday = new Date();
- yesterday.setDate(yesterday.getDate() - 1);
- if (data.userStats.lastActivityDate !== this.formatDate(yesterday)) {
- data.userStats.dailyStreak = 1;
- } else {
- data.userStats.dailyStreak += 1;
- }
- }
- }
-
- data.userStats.lastActivityDate = today;
- this.saveData(data);
- }
-
- getUserStats(): UserStats {
- const data = this.getData();
- return data.userStats;
- }
-
// Utility Methods
generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
@@ -795,8 +598,7 @@ export class StorageManager {
const isValid = issues.length === 0;
const hasPartialData = !isValid && typeof data === 'object' && data !== null &&
- (data.tasks !== undefined || data.projects !== undefined || data.version !== undefined ||
- data.habits !== undefined || data.userStats !== undefined || data.settings !== undefined);
+ (data.tasks !== undefined || data.projects !== undefined || data.version !== undefined);
return { isValid, hasPartialData, issues, parsed: data };
} catch (e) {
@@ -813,7 +615,6 @@ export class StorageManager {
const catObj = data.categories as unknown as Record;
categories = [...new Set([
...(catObj['tasks'] || []),
- ...(catObj['habits'] || []),
...(catObj['finance'] || [])
])];
} else if (Array.isArray(data.categories) && data.categories.length > 0) {
@@ -829,18 +630,10 @@ export class StorageManager {
lastUpdated: now,
tasks: Array.isArray(data.tasks) ? data.tasks : [],
projects: Array.isArray(data.projects) ? data.projects : [],
- habits: Array.isArray(data.habits) ? data.habits : [],
- dailyHabitLogs: Array.isArray(data.dailyHabitLogs) ? data.dailyHabitLogs : [],
expenses: Array.isArray(data.expenses) ? data.expenses : [],
revenue: Array.isArray(data.revenue) ? data.revenue : [],
charges: Array.isArray(data.charges) ? data.charges : [],
categories,
- userStats: {
- level: (data.userStats as LegacyUserStats)?.level ?? 1,
- dailyStreak: (data.userStats as LegacyUserStats)?.dailyStreak ?? 0,
- lastActivityDate: (data.userStats as LegacyUserStats)?.lastActivityDate ?? null,
- },
- settings: data.settings || { tasksPerLevel: 30 },
wishList: Array.isArray(data.wishList) ? data.wishList : [],
wishLists: Array.isArray(data.wishLists) ? data.wishLists : [],
notes: Array.isArray(data.notes) ? data.notes : [],
@@ -890,7 +683,6 @@ export class StorageManager {
const catObj = data.categories as unknown as Record;
const merged = [...new Set([
...(catObj['tasks'] || []),
- ...(catObj['habits'] || []),
...(catObj['finance'] || [])
])];
data.categories = merged;
@@ -925,7 +717,6 @@ export class StorageManager {
data.categories[idx] = trimmedNew;
// Propagate rename to all item types
- data.habits = data.habits.map(h => h.category === oldName ? { ...h, category: trimmedNew } : h);
data.expenses = data.expenses.map(e => e.category === oldName ? { ...e, category: trimmedNew } : e);
data.revenue = data.revenue.map(r => r.category === oldName ? { ...r, category: trimmedNew } : r);
if (data.charges) {
@@ -946,7 +737,6 @@ export class StorageManager {
data.categories.splice(idx, 1);
// Clear category from all item types
- data.habits = data.habits.map(h => h.category === categoryName ? { ...h, category: null } : h);
data.expenses = data.expenses.map(e => e.category === categoryName ? { ...e, category: null } : e);
data.revenue = data.revenue.map(r => r.category === categoryName ? { ...r, category: null } : r);
if (data.charges) {
@@ -957,31 +747,6 @@ export class StorageManager {
return true;
}
- // ========================
- // Settings Management
- // ========================
- getSettings(): Settings {
- const data = this.getData();
- // Ensure settings exist with defaults
- if (!data.settings) {
- data.settings = {
- tasksPerLevel: 30
- };
- this.saveData(data);
- }
- return data.settings;
- }
-
- updateSettings(settings: Partial): void {
- const data = this.getData();
- if (!data.settings) {
- data.settings = { tasksPerLevel: 30 };
- }
- Object.assign(data.settings, settings);
- // Recalculate level with new settings
- this.updateLevel();
- this.saveData(data);
- }
}
// Initialize global storage manager
diff --git a/tests/storage.test.ts b/tests/storage.test.ts
index f008ad3..61b3b59 100644
--- a/tests/storage.test.ts
+++ b/tests/storage.test.ts
@@ -1,6 +1,6 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { StorageManager, getDaysUntilDueText } from '../src/storage';
-import type { Task, Habit, FinanceItem } from '../src/storage';
+import type { Task, FinanceItem } from '../src/storage';
// Mock localStorage
const localStorageMock = (() => {
@@ -40,9 +40,7 @@ describe('StorageManager', () => {
expect(data.version).toBe('1.0.0');
expect(data.tasks).toEqual([]);
expect(data.projects).toEqual([]);
- expect(data.habits).toEqual([]);
expect(data.categories).toContain('Work');
- expect(data.userStats.level).toBe(1);
});
it('should not overwrite existing data', () => {
@@ -127,135 +125,6 @@ describe('StorageManager', () => {
});
});
- // ========================
- // Habit Management
- // ========================
- describe('habit management', () => {
- it('should add a habit', () => {
- const habit = storage.addHabit({ name: 'Exercise', icon: '๐ช' });
- expect(habit.id).toBeDefined();
- expect(habit.name).toBe('Exercise');
- expect(habit.streak).toBe(0);
- expect(habit.targetGoal).toBe(1);
- });
-
- it('should get all habits', () => {
- storage.addHabit({ name: 'Read' });
- storage.addHabit({ name: 'Meditate' });
- expect(storage.getHabits().length).toBe(2);
- });
-
- it('should update a habit', () => {
- const habit = storage.addHabit({ name: 'Read' });
- storage.updateHabit(habit.id, { name: 'Read Books', targetGoal: 3 });
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.name).toBe('Read Books');
- expect(updated?.targetGoal).toBe(3);
- });
-
- it('should delete a habit', () => {
- const habit = storage.addHabit({ name: 'To Delete' });
- storage.deleteHabit(habit.id);
- expect(storage.getHabits().length).toBe(0);
- });
-
- it('should log habit completion and set streak to 1 when targetGoal met', () => {
- const habit = storage.addHabit({ name: 'Exercise' });
- storage.logHabitCompletion(habit.id, new Date());
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.streak).toBe(1);
- });
-
- it('should not increment streak when targetGoal is not yet met', () => {
- const habit = storage.addHabit({ name: 'Drink Water', targetGoal: 10 });
- storage.logHabitCompletion(habit.id, new Date());
- storage.logHabitCompletion(habit.id, new Date());
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.streak).toBe(0);
- });
-
- it('should set streak to 1 when targetGoal is fully met', () => {
- const habit = storage.addHabit({ name: 'Drink Water', targetGoal: 3 });
- storage.logHabitCompletion(habit.id, new Date());
- storage.logHabitCompletion(habit.id, new Date());
- storage.logHabitCompletion(habit.id, new Date());
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.streak).toBe(1);
- });
-
- it('should count consecutive fully-completed days as streak', () => {
- const habit = storage.addHabit({ name: 'Exercise' });
- const day1 = new Date(2025, 0, 13);
- const day2 = new Date(2025, 0, 14);
- const day3 = new Date(2025, 0, 15);
- storage.logHabitCompletion(habit.id, day1);
- storage.logHabitCompletion(habit.id, day2);
- storage.logHabitCompletion(habit.id, day3);
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.streak).toBe(3);
- });
-
- it('should reset streak count when there is a gap between completed days', () => {
- const habit = storage.addHabit({ name: 'Exercise' });
- const day1 = new Date(2025, 0, 13);
- const day3 = new Date(2025, 0, 15);
- storage.logHabitCompletion(habit.id, day1);
- storage.logHabitCompletion(habit.id, day3);
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.streak).toBe(1);
- });
-
- it('should extend streak when retroactively completing a missed day', () => {
- const habit = storage.addHabit({ name: 'Exercise' });
- const day1 = new Date(2025, 0, 13);
- const day2 = new Date(2025, 0, 14);
- const day3 = new Date(2025, 0, 15);
- storage.logHabitCompletion(habit.id, day1);
- storage.logHabitCompletion(habit.id, day3);
- // Streak is 1 (gap at day2)
- storage.logHabitCompletion(habit.id, day2);
- // Now day1, day2, day3 are all complete โ streak = 3
- const updated = storage.getHabits().find(h => h.id === habit.id);
- expect(updated?.streak).toBe(3);
- });
-
- it('should count habit completions for today', () => {
- const habit = storage.addHabit({ name: 'Push-ups', targetGoal: 3 });
- storage.logHabitCompletion(habit.id, new Date());
- storage.logHabitCompletion(habit.id, new Date());
- expect(storage.countHabitCompletionsToday(habit.id)).toBe(2);
- });
-
- it('should check if habit is completed today', () => {
- const habit = storage.addHabit({ name: 'Meditate' });
- expect(storage.isHabitCompletedToday(habit.id)).toBe(false);
- storage.logHabitCompletion(habit.id, new Date());
- expect(storage.isHabitCompletedToday(habit.id)).toBe(true);
- });
-
- it('should count habit completions for a specific date', () => {
- const habit = storage.addHabit({ name: 'Exercise' });
- const date = new Date(2025, 0, 15);
- storage.logHabitCompletion(habit.id, date);
- storage.logHabitCompletion(habit.id, date);
- expect(storage.countHabitCompletionsForDate(habit.id, '2025-01-15')).toBe(2);
- expect(storage.countHabitCompletionsForDate(habit.id, '2025-01-16')).toBe(0);
- });
-
- it('should calculate streak correctly using calculateHabitStreak', () => {
- const habit = storage.addHabit({ name: 'Drink Water', targetGoal: 2 });
- const day1 = new Date(2025, 0, 13);
- const day2 = new Date(2025, 0, 14);
- storage.logHabitCompletion(habit.id, day1);
- storage.logHabitCompletion(habit.id, day1);
- storage.logHabitCompletion(habit.id, day2);
- storage.logHabitCompletion(habit.id, day2);
- const data = storage.getData();
- const streak = storage.calculateHabitStreak(habit.id, habit.targetGoal, data.dailyHabitLogs);
- expect(streak).toBe(2);
- });
- });
-
// ========================
// Finance Management
// ========================
@@ -308,27 +177,6 @@ describe('StorageManager', () => {
});
});
- // ========================
- // Leveling & Streak
- // ========================
- describe('leveling and streak', () => {
- it('should calculate level based on completed tasks', () => {
- // Default: 30 tasks per level
- for (let i = 0; i < 31; i++) {
- const task = storage.addTask({ title: `Task ${i}` });
- storage.updateTask(task.id, { completed: true });
- }
- storage.updateLevel();
- const data = storage.getData();
- expect(data.userStats.level).toBe(2); // 31 / 30 = 1.03, floor + 1 = 2
- });
-
- it('should update daily streak', () => {
- storage.updateDailyStreak(true);
- expect(storage.getUserStats().dailyStreak).toBe(1);
- });
- });
-
// ========================
// Categories
// ========================
@@ -356,12 +204,12 @@ describe('StorageManager', () => {
});
it('should update a category and propagate to items', () => {
- storage.addHabit({ name: 'Test', category: 'Work' });
+ storage.addExpense({ description: 'Test', amount: 10, category: 'Work' });
const result = storage.updateCategory('Work', 'Career');
expect(result).toBe(true);
expect(storage.getCategories()).toContain('Career');
expect(storage.getCategories()).not.toContain('Work');
- expect(storage.getHabits()[0].category).toBe('Career');
+ expect(storage.getExpenses()[0].category).toBe('Career');
});
it('should not update to an existing category name', () => {
@@ -370,26 +218,11 @@ describe('StorageManager', () => {
});
it('should delete a category and clear from items', () => {
- storage.addHabit({ name: 'Test', category: 'Work' });
+ storage.addExpense({ description: 'Test', amount: 10, category: 'Work' });
const result = storage.deleteCategory('Work');
expect(result).toBe(true);
expect(storage.getCategories()).not.toContain('Work');
- expect(storage.getHabits()[0].category).toBeNull();
- });
- });
-
- // ========================
- // Settings
- // ========================
- describe('settings management', () => {
- it('should return default settings', () => {
- const settings = storage.getSettings();
- expect(settings.tasksPerLevel).toBe(30);
- });
-
- it('should update settings', () => {
- storage.updateSettings({ tasksPerLevel: 50 });
- expect(storage.getSettings().tasksPerLevel).toBe(50);
+ expect(storage.getExpenses()[0].category).toBeNull();
});
});
@@ -501,21 +334,18 @@ describe('StorageManager', () => {
expect(data.version).toBe('1.0.0');
expect(data.tasks).toHaveLength(1);
expect(Array.isArray(data.projects)).toBe(true);
- expect(Array.isArray(data.habits)).toBe(true);
- expect(data.settings.tasksPerLevel).toBe(30);
});
it('should migrate old per-type category structure to flat array', () => {
const partial = {
tasks: [],
projects: [],
- categories: { tasks: ['Work', 'Personal'], habits: ['Fitness'], finance: ['Income'] }
+ categories: { tasks: ['Work', 'Personal'], finance: ['Income'] }
};
const result = storage.migrateAndImport(partial as any);
expect(result).toBe(true);
const categories = storage.getCategories();
expect(categories).toContain('Work');
- expect(categories).toContain('Fitness');
expect(categories).toContain('Income');
});