working leantime widget 19
This commit is contained in:
parent
bdca2c6252
commit
9b7f0bc78a
@ -8,8 +8,14 @@ interface Task {
|
|||||||
projectName: string;
|
projectName: string;
|
||||||
status: string;
|
status: string;
|
||||||
dueDate: string | null;
|
dueDate: string | null;
|
||||||
details?: string;
|
details?: string | null;
|
||||||
milestone?: string;
|
milestone?: string | null;
|
||||||
|
class: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Project {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache for user IDs
|
// Cache for user IDs
|
||||||
@ -132,68 +138,53 @@ export async function GET() {
|
|||||||
console.log('Projects response:', JSON.stringify(projectsData, null, 2));
|
console.log('Projects response:', JSON.stringify(projectsData, null, 2));
|
||||||
|
|
||||||
const projectsMap = new Map(
|
const projectsMap = new Map(
|
||||||
projectsData.result.map((project: any) => [project.id, project.name])
|
projectsData.result.map((project: Project) => [project.id, project.name])
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define status code mapping
|
|
||||||
const statusMapping: Record<string, string> = {
|
|
||||||
'-1': 'archived',
|
|
||||||
'0': 'new',
|
|
||||||
'1': 'in progress',
|
|
||||||
'2': 'waiting',
|
|
||||||
'3': 'in review',
|
|
||||||
'4': 'done'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Transform the nested structure into a flat array of tasks
|
// Transform the nested structure into a flat array of tasks
|
||||||
const tasks: Task[] = [];
|
const tasks: Task[] = [];
|
||||||
Object.entries(data.result).forEach(([projectId, statusGroups]) => {
|
Object.entries(data.result).forEach(([projectId, statusGroups]) => {
|
||||||
const projectName = projectsMap.get(Number(projectId)) || `Project ${projectId}`;
|
const project = projectsData.result.find((p: Project) => p.id === Number(projectId));
|
||||||
|
const projectName = project ? project.name : `Project ${projectId}`;
|
||||||
|
|
||||||
if (typeof statusGroups === 'object' && statusGroups !== null) {
|
if (typeof statusGroups === 'object' && statusGroups !== null) {
|
||||||
// Iterate through each status group
|
// Iterate through each status group
|
||||||
Object.entries(statusGroups).forEach(([statusType, statusLabels]) => {
|
Object.entries(statusGroups).forEach(([_, label]) => {
|
||||||
if (typeof statusLabels === 'object' && statusLabels !== null) {
|
if (typeof label === 'object' && label !== null && 'name' in label) {
|
||||||
// Each status label in the group
|
const statusLabel = label as {
|
||||||
Object.values(statusLabels).forEach((label: any) => {
|
name: string;
|
||||||
const statusName = statusMapping[statusType] || `status-${statusType}`;
|
class: string;
|
||||||
const headline = String(label.title || label.name || statusName);
|
statusType: string;
|
||||||
|
kanbanCol: string;
|
||||||
tasks.push({
|
sortKey: string;
|
||||||
id: label.id?.toString() || `${projectId}-${statusType}`,
|
};
|
||||||
headline,
|
|
||||||
projectName,
|
tasks.push({
|
||||||
status: statusName,
|
id: `${projectId}-${statusLabel.sortKey}`,
|
||||||
dueDate: null,
|
headline: statusLabel.name,
|
||||||
details: label.description || null,
|
projectName,
|
||||||
milestone: label.milestone || null
|
status: statusLabel.statusType.toLowerCase(),
|
||||||
});
|
dueDate: null,
|
||||||
|
details: undefined,
|
||||||
|
milestone: undefined,
|
||||||
|
class: statusLabel.class
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort tasks by status type
|
// Sort tasks by their sortKey
|
||||||
const statusOrder: Record<string, number> = {
|
|
||||||
'new': 1,
|
|
||||||
'in progress': 2,
|
|
||||||
'waiting': 3,
|
|
||||||
'in review': 4,
|
|
||||||
'done': 5,
|
|
||||||
'archived': 6
|
|
||||||
};
|
|
||||||
|
|
||||||
tasks.sort((a, b) => {
|
tasks.sort((a, b) => {
|
||||||
const statusA = statusOrder[a.status.toLowerCase()] || 99;
|
const sortKeyA = Number(a.id.split('-')[1]) || 99;
|
||||||
const statusB = statusOrder[b.status.toLowerCase()] || 99;
|
const sortKeyB = Number(b.id.split('-')[1]) || 99;
|
||||||
return statusA - statusB;
|
return sortKeyA - sortKeyB;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deduplicate tasks based on project and status
|
// Deduplicate tasks based on headline and status
|
||||||
const uniqueTasks = tasks.reduce((acc: Task[], task) => {
|
const uniqueTasks = tasks.reduce((acc: Task[], task) => {
|
||||||
const existingTask = acc.find(t =>
|
const existingTask = acc.find(t =>
|
||||||
t.projectName === task.projectName &&
|
t.headline === task.headline &&
|
||||||
t.status === task.status
|
t.status === task.status
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { useEffect, useState } from "react";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { RefreshCw } from "lucide-react";
|
import { RefreshCw } from "lucide-react";
|
||||||
import { formatDate } from "@/lib/utils";
|
|
||||||
|
|
||||||
interface Task {
|
interface Task {
|
||||||
id: string;
|
id: string;
|
||||||
@ -12,8 +11,9 @@ interface Task {
|
|||||||
projectName: string;
|
projectName: string;
|
||||||
status: string;
|
status: string;
|
||||||
dueDate: string | null;
|
dueDate: string | null;
|
||||||
details?: string;
|
details?: string | null;
|
||||||
milestone?: string;
|
milestone?: string | null;
|
||||||
|
class: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Flow() {
|
export function Flow() {
|
||||||
@ -63,25 +63,6 @@ export function Flow() {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const getStatusColor = (status: string) => {
|
|
||||||
switch (status.toLowerCase()) {
|
|
||||||
case 'new':
|
|
||||||
return 'text-blue-600';
|
|
||||||
case 'blocked':
|
|
||||||
return 'text-red-600';
|
|
||||||
case 'in progress':
|
|
||||||
case 'inprogress':
|
|
||||||
return 'text-orange-600';
|
|
||||||
case 'done':
|
|
||||||
case 'archived':
|
|
||||||
return 'text-green-600';
|
|
||||||
case 'waiting for approval':
|
|
||||||
return 'text-yellow-600';
|
|
||||||
default:
|
|
||||||
return 'text-gray-600';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Group tasks by project
|
// Group tasks by project
|
||||||
const groupedTasks = tasks.reduce((acc, task) => {
|
const groupedTasks = tasks.reduce((acc, task) => {
|
||||||
if (!acc[task.projectName]) {
|
if (!acc[task.projectName]) {
|
||||||
@ -124,18 +105,8 @@ export function Flow() {
|
|||||||
<div key={task.id} className="flex justify-between items-start text-sm border-b border-gray-100 pb-2">
|
<div key={task.id} className="flex justify-between items-start text-sm border-b border-gray-100 pb-2">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium">{task.headline}</div>
|
<div className="font-medium">{task.headline}</div>
|
||||||
{task.milestone && (
|
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
|
||||||
{task.milestone}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{task.dueDate && (
|
|
||||||
<div className="text-xs text-gray-500 mt-1">
|
|
||||||
{formatDate(task.dueDate)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className={`ml-4 ${getStatusColor(task.status)}`}>
|
<div className={`ml-4 px-2 py-1 rounded text-xs ${getLabelClass(task.class)}`}>
|
||||||
{task.status.toUpperCase()}
|
{task.status.toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -148,4 +119,18 @@ export function Flow() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLabelClass(className: string): string {
|
||||||
|
const classMap: Record<string, string> = {
|
||||||
|
'label-default': 'bg-gray-100 text-gray-800',
|
||||||
|
'label-success': 'bg-green-100 text-green-800',
|
||||||
|
'label-warning': 'bg-yellow-100 text-yellow-800',
|
||||||
|
'label-info': 'bg-blue-100 text-blue-800',
|
||||||
|
'label-blue': 'bg-blue-100 text-blue-800',
|
||||||
|
'label-darker-blue': 'bg-indigo-100 text-indigo-800',
|
||||||
|
'label-dark-green': 'bg-emerald-100 text-emerald-800',
|
||||||
|
};
|
||||||
|
|
||||||
|
return classMap[className] || 'bg-gray-100 text-gray-800';
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user