gitshark

Clone repository

git clone https://git.vilip.de/git/vilip/taskflow.git
git clone git@git.vilip.de:vilip/taskflow.git
/**
 * Task Service
 * Business logic for task management
 */

import { Task, TaskStatus } from '../types/gtd';
import storageService from './storage.service';
import { generateId } from '../utils/id-generator';
import { computeNextDueDate } from '../utils/recurrence';

class TaskService {
  /**
   * Get all tasks
   */
  async getAllTasks(): Promise<Task[]> {
    const data = await storageService.getData();
    return data.tasks;
  }

  /**
   * Get tasks by status
   */
  async getTasksByStatus(status: TaskStatus): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    return tasks.filter(task => task.status === status && !task.completed);
  }

  /**
   * Get inbox tasks (tasks without due date - unscheduled)
   */
  async getInboxTasks(): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    return tasks.filter(task => 
      !task.completed && 
      !task.dueDate &&
      task.status !== 'someday'
    );
  }

  /**
   * Get today's tasks
   */
  async getTodayTasks(): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);

    return tasks.filter(task => {
      if (task.completed) return false;
      if (!task.dueDate) return false;
      const dueDate = new Date(task.dueDate);
      return dueDate >= today && dueDate < tomorrow;
    });
  }

  /**
   * Get tomorrow's tasks
   */
  async getTomorrowTasks(): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);
    tomorrow.setHours(0, 0, 0, 0);
    const dayAfter = new Date(tomorrow);
    dayAfter.setDate(dayAfter.getDate() + 1);

    return tasks.filter(task => {
      if (task.completed) return false;
      if (!task.dueDate) return false;
      const dueDate = new Date(task.dueDate);
      return dueDate >= tomorrow && dueDate < dayAfter;
    });
  }

  /**
   * Get overdue tasks
   */
  async getOverdueTasks(): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    return tasks.filter(task => {
      if (task.completed) return false;
      if (!task.dueDate) return false;
      const dueDate = new Date(task.dueDate);
      return dueDate < today;
    });
  }

  /**
   * Get tasks by project
   */
  async getTasksByProject(projectId: string): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    return tasks.filter(task => task.projectId === projectId && !task.completed);
  }

  /**
   * Get tasks by tag
   */
  async getTasksByTag(tagId: string): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    return tasks.filter(task => task.tagIds.includes(tagId) && !task.completed);
  }

  /**
   * Get completed tasks
   */
  async getCompletedTasks(): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    return tasks.filter(task => task.completed).sort((a, b) => 
      (b.completedAt || 0) - (a.completedAt || 0)
    );
  }

  /**
   * Create a new task
   */
  async createTask(taskData: Partial<Task>): Promise<Task> {
    const data = await storageService.getData();
    
    const newTask: Task = {
      id: generateId(),
      title: taskData.title || '',
      description: taskData.description,
      status: taskData.status,
      priority: taskData.priority,
      dueDate: taskData.dueDate,
      projectId: taskData.projectId,
      tagIds: taskData.tagIds || [],
      recurrence: taskData.recurrence,
      completed: false,
      createdAt: Date.now(),
      updatedAt: Date.now(),
    };

    data.tasks.push(newTask);
    await storageService.saveData(data);
    
    return newTask;
  }

  /**
   * Update a task
   */
  async updateTask(taskId: string, updates: Partial<Task>): Promise<Task> {
    const data = await storageService.getData();
    const taskIndex = data.tasks.findIndex(t => t.id === taskId);
    
    if (taskIndex === -1) {
      throw new Error('Task not found');
    }

    const currentTask = data.tasks[taskIndex];
    const updatedTask = {
      ...currentTask,
      ...updates,
      id: taskId,
      updatedAt: Date.now(),
    };

    data.tasks[taskIndex] = updatedTask;
    await storageService.saveData(data);
    
    return updatedTask;
  }

  /**
   * Get all repeating (recurring) tasks
   */
  async getRepeatingTasks(): Promise<Task[]> {
    const tasks = await this.getAllTasks();
    return tasks.filter(task => task.recurrence);
  }

  /**
   * Mark task as complete.
   *
   * For a repeating task we don't mark it done — instead we advance its due
   * date to the next occurrence so it disappears now and reappears when due.
   */
  async completeTask(taskId: string): Promise<Task> {
    const data = await storageService.getData();
    const task = data.tasks.find(t => t.id === taskId);

    if (task?.recurrence) {
      const base = task.dueDate ?? Date.now();
      return this.updateTask(taskId, {
        dueDate: computeNextDueDate(base, task.recurrence),
        completed: false,
        completedAt: undefined,
      });
    }

    return this.updateTask(taskId, {
      completed: true,
      completedAt: Date.now(),
    });
  }

  /**
   * Mark task as incomplete
   */
  async uncompleteTask(taskId: string): Promise<Task> {
    return this.updateTask(taskId, {
      completed: false,
      completedAt: undefined,
    });
  }

  /**
   * Move task to different status
   */
  async moveTask(taskId: string, status: TaskStatus): Promise<Task> {
    return this.updateTask(taskId, { status });
  }

  /**
   * Delete a task
   */
  async deleteTask(taskId: string): Promise<void> {
    const data = await storageService.getData();
    data.tasks = data.tasks.filter(t => t.id !== taskId);
    await storageService.saveData(data);
  }

  /**
   * Archive completed tasks older than specified days
   */
  async archiveOldCompletedTasks(daysOld: number = 30): Promise<number> {
    const data = await storageService.getData();
    const cutoffDate = Date.now() - (daysOld * 24 * 60 * 60 * 1000);
    
    const beforeCount = data.tasks.length;
    data.tasks = data.tasks.filter(task => {
      if (!task.completed) return true;
      if (!task.completedAt) return true;
      return task.completedAt > cutoffDate;
    });
    
    await storageService.saveData(data);
    return beforeCount - data.tasks.length;
  }
}

export default new TaskService();

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog