diff --git a/README.md b/README.md index 5c3ac1f..bc3e1e5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ # Task Manager Pro ๐Ÿ“‹ -A comprehensive Progressive Web App (PWA) for managing tasks, projects, habits, and finances with gamification features. +A comprehensive Progressive Web App (PWA) for managing tasks, projects, and finances. ## ๐Ÿ“– Project Description -Task Manager Pro is a powerful, all-in-one productivity application that combines task management, project organization, habit tracking, and personal finance management into a single, intuitive interface. Built as a Progressive Web App, it works seamlessly across all devices and can be installed on your phone or computer for offline access. - -The app features a gamification system with points, levels, and daily streaks to keep you motivated, along with a customizable rewards shop where you can redeem your earned points. +Task Manager Pro is a powerful, all-in-one productivity application that combines task management, project organization, and personal finance management into a single, intuitive interface. Built as a Progressive Web App, it works seamlessly across all devices and can be installed on your phone or computer for offline access. ## โœจ Current Features ### ๐ŸŽฏ Task Management - Create, edit, and delete tasks with detailed information -- Set due dates, priorities (low, medium, high), and point values +- Set due dates and repeat schedules - Categorize tasks with custom categories - Filter and search tasks by category, status, or keywords - **Recurring Tasks** with multiple options: @@ -31,15 +29,6 @@ The app features a gamification system with points, levels, and daily streaks to - Organize tasks within projects - Monitor active projects on dashboard -### โญ Habit Tracking -- Create daily habits with custom icons (emoji picker) -- Set target completion goals per day -- Track habit streaks -- View habit history and completion patterns -- Quick habit logging from dashboard -- Filter habits by custom categories -- Monitor incomplete habits for the day - ### ๐Ÿ’ฐ Finance Management - Track three types of financial records: - **Expenses**: Daily spending and costs @@ -50,21 +39,8 @@ The app features a gamification system with points, levels, and daily streaks to - View financial summaries and totals - Add descriptions and amounts for each entry -### ๐ŸŽฎ Gamification System -- Earn points by completing tasks and habits -- Level up based on accumulated points -- Maintain daily streaks for consecutive days -- Visual progress tracking in header - -### ๐Ÿ›’ Rewards Shop -- Create custom rewards with point costs -- Redeem rewards using earned points -- Manage and edit available rewards -- Track when rewards are purchased - ### ๐Ÿ  Dashboard -- Today's overview with task and habit summaries -- Quick access to incomplete habits +- Today's overview with task summaries - Recent activity feed - Active projects summary - Real-time statistics display @@ -206,17 +182,12 @@ npm run test:e2e:ui # Run E2E tests with the Playwright UI - Click "+ Add Task" - Fill in the details and save -3. **Set Up Daily Habits** - - Navigate to the Habits tab - - Create habits you want to track daily - - Complete them to earn points and build streaks - -4. **Track Your Finances** +3. **Track Your Finances** - Use the Finances tab to log expenses and revenue - Set up recurring charges for bills - Filter by date range to see spending patterns -5. **Organize with Projects** +4. **Organize with Projects** - Create projects to group related tasks - Assign tasks to projects for better organization - Monitor progress from the dashboard diff --git a/css/styles.css b/css/styles.css index 04170f3..90ecb9d 100644 --- a/css/styles.css +++ b/css/styles.css @@ -64,27 +64,6 @@ html, body { font-weight: 700; } -.header-stats { - display: flex; - gap: 2rem; -} - -.stat { - display: flex; - flex-direction: column; - align-items: center; -} - -.stat-label { - font-size: 0.875rem; - opacity: 0.8; -} - -.stat-value { - font-size: 1.5rem; - font-weight: 700; -} - /* Date Navigator */ .date-navigator { background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); @@ -709,117 +688,6 @@ html, body { color: var(--primary-color); } -/* ======================== - Habits - ======================== */ -.habits-container { - max-width: 900px; - margin: 0 auto; -} - -.habits-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; -} - -.habits-header h2 { - font-size: 1.875rem; -} - -.habits-list { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: 1.5rem; -} - -.habit-card { - background: white; - border: 1px solid var(--border-color); - border-radius: 0.5rem; - padding: 1.5rem; - cursor: pointer; - transition: all 0.3s ease; -} - -.habit-card:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-lg); -} - -.habit-icon { - font-size: 2.5rem; - margin-bottom: 0.5rem; -} - -.habit-name { - font-size: 1.125rem; - font-weight: 600; - margin-bottom: 0.5rem; -} - -.habit-description { - font-size: 0.875rem; - color: var(--text-light); - margin-bottom: 1rem; -} - -.habit-stats { - display: flex; - justify-content: space-between; - padding: 1rem; - background: var(--light-bg); - border-radius: 0.375rem; - margin-bottom: 1rem; - font-size: 0.875rem; -} - -.habit-stat { - text-align: center; -} - -.habit-stat-label { - color: var(--text-light); - font-size: 0.75rem; -} - -.habit-stat-value { - font-weight: 700; - color: var(--primary-color); -} - -.habit-checkbox { - width: 100%; - padding: 0.5rem; - background: var(--light-bg); - border: 2px solid var(--primary-color); - border-radius: 0.375rem; - color: var(--primary-color); - font-weight: 600; - cursor: pointer; - transition: all 0.2s ease; -} - -.habit-checkbox:hover:not(:disabled) { - background: var(--primary-color); - color: white; -} - -.habit-checkbox.completed { - background: var(--secondary-color); - border-color: var(--secondary-color); - color: white; -} - -.habit-checkbox:disabled { - background: var(--light-bg); - border-color: #d1d5db; - color: #9ca3af; - cursor: not-allowed; - opacity: 0.6; -} - /* Days of Week Selector */ .days-selector { display: grid; @@ -1221,66 +1089,6 @@ html, body { margin-top: 1.5rem; } -/* Emoji Selector */ -.emoji-selector { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.emoji-display { - font-size: 2rem; - width: 60px; - height: 60px; - display: flex; - align-items: center; - justify-content: center; - background: var(--light-bg); - border: 2px solid var(--border-color); - border-radius: 0.5rem; - padding: 0.5rem; -} - -#habitEmojiBtn { - flex: 1; -} - -/* Emoji Picker Modal */ -.emoji-picker-modal { - max-width: 500px; - max-height: 600px; - display: flex; - flex-direction: column; -} - -.emoji-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 0.75rem; - padding: 1rem; - overflow-y: auto; - flex: 1; -} - -.emoji-btn { - padding: 1rem; - font-size: 2.5rem; - border: 1px solid var(--border-color); - background: var(--light-bg); - border-radius: 0.375rem; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - justify-content: center; -} - -.emoji-btn:hover { - background: var(--primary-color); - border-color: var(--primary-color); - transform: scale(1.1); -} - /* ======================== Modals ======================== */ @@ -1487,11 +1295,6 @@ html, body { gap: 1rem; } - .header-stats { - width: 100%; - justify-content: space-around; - } - .hamburger-menu { display: flex; } diff --git a/e2e/app.spec.ts b/e2e/app.spec.ts index 2a2f92b..34d9bad 100644 --- a/e2e/app.spec.ts +++ b/e2e/app.spec.ts @@ -21,11 +21,6 @@ test.describe('Task Manager App', () => { await expect(page.locator('#dashboard-tab')).toBeVisible(); }); - test('should display header stats', async ({ page }) => { - await expect(page.locator('#userLevel')).toHaveText('1'); - await expect(page.locator('#dailyStreak')).toHaveText('0'); - }); - test('should switch to tasks tab', async ({ page }) => { await page.click('[data-tab="tasks"]'); await expect(page.locator('#tasks-tab')).toBeVisible(); @@ -37,11 +32,6 @@ test.describe('Task Manager App', () => { await expect(page.locator('#projects-tab')).toBeVisible(); }); - test('should switch to habits tab', async ({ page }) => { - await page.click('[data-tab="habits"]'); - await expect(page.locator('#habits-tab')).toBeVisible(); - }); - test('should switch to finances tab', async ({ page }) => { await page.click('[data-tab="finances"]'); await expect(page.locator('#finances-tab')).toBeVisible(); @@ -152,41 +142,6 @@ test.describe('Task Manager App', () => { }); }); - // ======================== - // Habit CRUD - // ======================== - test.describe('habit management', () => { - test.beforeEach(async ({ page }) => { - await page.click('[data-tab="habits"]'); - }); - - test('should show empty state initially', async ({ page }) => { - await expect(page.locator('#habitsList .empty-state')).toBeVisible(); - }); - - test('should create a new habit', async ({ page }) => { - await page.click('#addHabitBtn'); - await page.fill('#habitName', 'Exercise'); - await page.fill('#habitDescription', '30 min workout'); - await page.click('#habitForm button[type="submit"]'); - - await expect(page.locator('#habitModal')).not.toHaveClass(/active/); - await expect(page.locator('.habit-card')).toBeVisible(); - await expect(page.locator('.habit-name')).toContainText('Exercise'); - }); - - test('should complete a habit', async ({ page }) => { - // Create habit - await page.click('#addHabitBtn'); - await page.fill('#habitName', 'Meditate'); - await page.click('#habitForm button[type="submit"]'); - - // Complete it - await page.click('.habit-checkbox'); - await expect(page.locator('.habit-checkbox')).toContainText('Done for Today'); - }); - }); - // ======================== // Finance CRUD // ======================== @@ -255,17 +210,17 @@ test.describe('Task Manager App', () => { }); test('should reload active tab data when returning to today', async ({ page }) => { - // Switch to habits tab - await page.click('[data-tab="habits"]'); - await expect(page.locator('#habits-tab')).toBeVisible(); + // Switch to tasks tab + await page.click('[data-tab="tasks"]'); + await expect(page.locator('#tasks-tab')).toBeVisible(); // Navigate to previous day await page.click('#prevDayBtn'); await expect(page.locator('#selectedDateDisplay')).not.toContainText('Today'); // Return to today via button await page.click('#goTodayBtn'); - // The habits tab should still be active and showing today's data + // The tasks tab should still be active and showing today's data await expect(page.locator('#selectedDateDisplay')).toContainText('Today'); - await expect(page.locator('#habits-tab')).toBeVisible(); + await expect(page.locator('#tasks-tab')).toBeVisible(); }); }); @@ -358,17 +313,6 @@ test.describe('Task Manager App', () => { test('should show data version', async ({ page }) => { await expect(page.locator('#dataVersion')).toHaveText('1.0.0'); }); - - test('should update tasks per level', async ({ page }) => { - page.on('dialog', dialog => dialog.accept()); - await page.fill('#tasksPerLevel', '50'); - await page.click('#saveTasksPerLevel'); - // Verify it persisted - await page.click('[data-tab="dashboard"]'); - await page.click('[data-tab="settings"]'); - const value = await page.inputValue('#tasksPerLevel'); - expect(value).toBe('50'); - }); }); // ======================== diff --git a/index.html b/index.html index 222527a..72a2d26 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ Task Manager - + @@ -25,17 +25,6 @@

๐Ÿ“‹ Task Manager

-
-
- Level - 1 - 0/30 tasks -
-
- Streak - 0 -
-
@@ -59,7 +48,6 @@

๐Ÿ“‹ Task Manager

- @@ -88,13 +76,6 @@

Today's Overview

Completed Today 0 -
- Incomplete Habits - 0 -
- -
-
@@ -305,98 +286,6 @@

Related Tasks

- -
-
-
-

Daily Habits

- -
- - -
-

No habits yet. Create daily habits to build streaks!

-
-
- - - - - - -
-
@@ -683,30 +572,6 @@

Add Shopping Item

Settings

-
-

Leveling System

-
- -
- - -
-

Number of completed tasks required to reach the next level (default: 30)

-
-
-

Current Status

-

- Current Level: 1 -

-

- Total Completed Tasks: 0 -

-

- Tasks to Next Level: 30 -

-
-
-

Data Management

@@ -731,7 +596,7 @@

Data Management

Category Management

-

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'); });