200 lines
5.2 KiB
TypeScript
200 lines
5.2 KiB
TypeScript
import { afterNextRender, ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
|
|
import { TaskStore } from '../../data-access/store/task-store';
|
|
import { Task, TaskState } from '../../data-access/models/task.model';
|
|
import { TaskDetailSidebar } from '../../ui/task-detail-sidebar/task-detail-sidebar';
|
|
import { TaskCreateSidebar } from '../../ui/task-create-sidebar/task-create-sidebar';
|
|
import { TaskBoardColumn } from '../../ui/task-board-column/task-board-column';
|
|
import { Pagination } from '@shared/ui/pagination/pagination';
|
|
|
|
export type ViewMode = 'table' | 'board' | 'timeline';
|
|
|
|
@Component({
|
|
selector: 'emi-task-list-page',
|
|
imports: [TaskDetailSidebar, TaskCreateSidebar, TaskBoardColumn, Pagination],
|
|
templateUrl: './task-list-page.html',
|
|
styleUrl: './task-list-page.css',
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
})
|
|
export class TaskListPage {
|
|
private readonly store = inject(TaskStore);
|
|
|
|
readonly PAGE_SIZE = 5;
|
|
|
|
constructor() {
|
|
afterNextRender(() => {
|
|
this.store.loadTasks();
|
|
});
|
|
}
|
|
|
|
readonly tasks = this.store.filteredTasks;
|
|
readonly loading = this.store.loading;
|
|
readonly selectedTask = this.store.selectedTask;
|
|
readonly transitioningState = this.store.transitioningState;
|
|
readonly showCreateSidebar = signal(false);
|
|
readonly activeView = signal<ViewMode>('table');
|
|
readonly currentPage = signal(1);
|
|
|
|
private readonly stateProgress: Record<TaskState, number> = {
|
|
new: 0,
|
|
active: 50,
|
|
resolved: 100,
|
|
closed: 100,
|
|
};
|
|
|
|
private readonly stateLabels: Record<TaskState, string> = {
|
|
new: 'New',
|
|
active: 'Active',
|
|
resolved: 'Resolved',
|
|
closed: 'Closed',
|
|
};
|
|
|
|
readonly totalPages = computed(() => {
|
|
return Math.max(1, Math.ceil(this.tasks().length / this.PAGE_SIZE));
|
|
});
|
|
|
|
readonly paginatedTasks = computed(() => {
|
|
const start = (this.currentPage() - 1) * this.PAGE_SIZE;
|
|
return this.tasks().slice(start, start + this.PAGE_SIZE);
|
|
});
|
|
|
|
readonly completionPercent = computed(() => {
|
|
const all = this.tasks();
|
|
if (all.length === 0) return 0;
|
|
const totalProgress = all.reduce((sum, t) => sum + this.getStateProgress(t), 0);
|
|
return Math.round(totalProgress / all.length);
|
|
});
|
|
|
|
readonly completedCount = computed(() =>
|
|
this.tasks().filter(t => this.isFinalized(t)).length
|
|
);
|
|
|
|
readonly activeCount = computed(() =>
|
|
this.tasks().filter(t => this.getState(t) === 'active').length
|
|
);
|
|
|
|
readonly newCount = computed(() =>
|
|
this.tasks().filter(t => this.getState(t) === 'new').length
|
|
);
|
|
|
|
readonly tasksByState = computed(() => {
|
|
const grouped: Record<TaskState, Task[]> = {
|
|
new: [],
|
|
active: [],
|
|
resolved: [],
|
|
closed: [],
|
|
};
|
|
for (const task of this.tasks()) {
|
|
const state = this.getState(task);
|
|
grouped[state].push(task);
|
|
}
|
|
return grouped;
|
|
});
|
|
|
|
readonly tasksByDate = computed(() => {
|
|
const sorted = [...this.tasks()].sort((a, b) =>
|
|
new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime()
|
|
);
|
|
const grouped: Record<string, Task[]> = {};
|
|
for (const task of sorted) {
|
|
const date = task.dueDate;
|
|
if (!grouped[date]) {
|
|
grouped[date] = [];
|
|
}
|
|
grouped[date].push(task);
|
|
}
|
|
return grouped;
|
|
});
|
|
|
|
readonly dueDates = computed(() => Object.keys(this.tasksByDate()));
|
|
|
|
onPageChange(page: number): void {
|
|
this.currentPage.set(page);
|
|
}
|
|
|
|
onViewChange(view: ViewMode): void {
|
|
this.activeView.set(view);
|
|
}
|
|
|
|
getState(task: Task): TaskState {
|
|
const history = task.stateHistory;
|
|
return history[history.length - 1]?.state ?? 'new';
|
|
}
|
|
|
|
getStateLabel(task: Task): string {
|
|
return this.stateLabels[this.getState(task)];
|
|
}
|
|
|
|
getStateProgress(task: Task): number {
|
|
return this.stateProgress[this.getState(task)];
|
|
}
|
|
|
|
isFinalized(task: Task): boolean {
|
|
const state = this.getState(task);
|
|
return state === 'resolved' || state === 'closed';
|
|
}
|
|
|
|
isSelected(task: Task): boolean {
|
|
return this.selectedTask()?.id === task.id;
|
|
}
|
|
|
|
onViewTask(task: Task): void {
|
|
this.showCreateSidebar.set(false);
|
|
this.store.setSelectedTask(task);
|
|
}
|
|
|
|
onCloseDetailSidebar(): void {
|
|
this.store.setSelectedTask(null);
|
|
}
|
|
|
|
onChangeState(state: TaskState): void {
|
|
const task = this.selectedTask();
|
|
if (task) {
|
|
this.store.transitionTask(task.id, state);
|
|
}
|
|
}
|
|
|
|
onDeleteTask(task: Task): void {
|
|
this.store.deleteTask(task.id);
|
|
this.store.setSelectedTask(null);
|
|
}
|
|
|
|
onAddNote(content: string): void {
|
|
const task = this.selectedTask();
|
|
if (task) {
|
|
this.store.addNote(task.id, content);
|
|
}
|
|
}
|
|
|
|
onDeleteNote(index: number): void {
|
|
const task = this.selectedTask();
|
|
if (task) {
|
|
this.store.deleteNote(task.id, index);
|
|
}
|
|
}
|
|
|
|
onOpenCreateSidebar(): void {
|
|
this.store.setSelectedTask(null);
|
|
this.showCreateSidebar.set(true);
|
|
}
|
|
|
|
onCloseCreateSidebar(): void {
|
|
this.showCreateSidebar.set(false);
|
|
}
|
|
|
|
onTaskCreated(): void {
|
|
this.showCreateSidebar.set(false);
|
|
}
|
|
|
|
onBoardViewTask(task: Task): void {
|
|
this.onViewTask(task);
|
|
}
|
|
|
|
onBoardEditTask(task: Task): void {
|
|
this.onViewTask(task);
|
|
}
|
|
|
|
onBoardDeleteTask(task: Task): void {
|
|
this.onDeleteTask(task);
|
|
}
|
|
}
|