140 lines
4.2 KiB
TypeScript
140 lines
4.2 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { isValidDateString } from "@/lib/utils/date-utils";
|
|
|
|
export interface Task {
|
|
id: number | string;
|
|
headline: string;
|
|
description?: string;
|
|
dateToFinish?: string | null;
|
|
dueDate?: string | null;
|
|
projectId: number;
|
|
projectName: string;
|
|
status: number;
|
|
editorId?: string;
|
|
editorFirstname?: string;
|
|
editorLastname?: string;
|
|
authorFirstname?: string;
|
|
authorLastname?: string;
|
|
milestone?: string | null;
|
|
milestoneHeadline?: string;
|
|
editTo?: string;
|
|
editFrom?: string;
|
|
type?: string;
|
|
dependingTicketId?: number | null;
|
|
createdOn?: string;
|
|
editedOn?: string | null;
|
|
assignedTo?: number[];
|
|
}
|
|
|
|
export interface UseTasksOptions {
|
|
limit?: number;
|
|
includeDone?: boolean;
|
|
sortField?: 'dateToFinish' | 'createdOn' | 'status';
|
|
sortDirection?: 'asc' | 'desc';
|
|
}
|
|
|
|
export function useTasks({
|
|
limit = 10,
|
|
includeDone = false,
|
|
sortField = 'dateToFinish',
|
|
sortDirection = 'asc'
|
|
}: UseTasksOptions = {}) {
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
|
|
|
const refresh = () => {
|
|
setRefreshTrigger(prev => prev + 1);
|
|
};
|
|
|
|
useEffect(() => {
|
|
const fetchTasks = async () => {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const response = await fetch('/api/leantime/tasks');
|
|
if (!response.ok) {
|
|
throw new Error('Failed to fetch tasks');
|
|
}
|
|
const data = await response.json();
|
|
|
|
if (!Array.isArray(data) && !Array.isArray(data.tasks)) {
|
|
setTasks([]);
|
|
return;
|
|
}
|
|
|
|
// Handle both API response formats
|
|
const tasksArray = Array.isArray(data) ? data : data.tasks;
|
|
|
|
// Filter and sort tasks
|
|
const filteredTasks = tasksArray
|
|
.filter((task: Task) => {
|
|
// Include done tasks only if specified
|
|
if (!includeDone && task.status === 5) {
|
|
return false;
|
|
}
|
|
return true;
|
|
})
|
|
.sort((a: Task, b: Task) => {
|
|
// Sort by date
|
|
if (sortField === 'dateToFinish') {
|
|
const dateA = getValidDate(a);
|
|
const dateB = getValidDate(b);
|
|
|
|
// If both dates are valid, compare them
|
|
if (dateA && dateB) {
|
|
const timeA = new Date(dateA).getTime();
|
|
const timeB = new Date(dateB).getTime();
|
|
if (timeA !== timeB) {
|
|
return sortDirection === 'asc' ? timeA - timeB : timeB - timeA;
|
|
}
|
|
}
|
|
|
|
// If only one date is valid, put the task with a date first
|
|
if (dateA) return sortDirection === 'asc' ? -1 : 1;
|
|
if (dateB) return sortDirection === 'asc' ? 1 : -1;
|
|
}
|
|
|
|
// Sort by created date
|
|
if (sortField === 'createdOn') {
|
|
const dateA = a.createdOn ? new Date(a.createdOn).getTime() : 0;
|
|
const dateB = b.createdOn ? new Date(b.createdOn).getTime() : 0;
|
|
return sortDirection === 'asc' ? dateA - dateB : dateB - dateA;
|
|
}
|
|
|
|
// Sort by status
|
|
if (sortField === 'status') {
|
|
const statusA = a.status || 0;
|
|
const statusB = b.status || 0;
|
|
return sortDirection === 'asc' ? statusA - statusB : statusB - statusA;
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
setTasks(limit ? filteredTasks.slice(0, limit) : filteredTasks);
|
|
} catch (error) {
|
|
setError(error instanceof Error ? error.message : 'Failed to fetch tasks');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
fetchTasks();
|
|
}, [limit, includeDone, sortField, sortDirection, refreshTrigger]);
|
|
|
|
return { tasks, loading, error, refresh };
|
|
}
|
|
|
|
// Helper function
|
|
function getValidDate(task: Task): string | null {
|
|
if (task.dateToFinish && isValidDateString(task.dateToFinish)) {
|
|
return task.dateToFinish;
|
|
}
|
|
if (task.dueDate && isValidDateString(task.dueDate)) {
|
|
return task.dueDate;
|
|
}
|
|
return null;
|
|
}
|