diff --git a/app/api/leantime/tasks/route.ts b/app/api/leantime/tasks/route.ts
new file mode 100644
index 00000000..4608ad2c
--- /dev/null
+++ b/app/api/leantime/tasks/route.ts
@@ -0,0 +1,62 @@
+import { getServerSession } from "next-auth/next";
+import { authOptions } from "@/app/api/auth/[...nextauth]/route";
+import { NextResponse } from "next/server";
+
+export async function GET() {
+ const session = await getServerSession(authOptions);
+
+ if (!session) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ try {
+ // Get user's tasks from Leantime
+ const response = await fetch('https://agilite.slm-lab.net/api/jsonrpc', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-API-Key': process.env.LEANTIME_TOKEN || '',
+ },
+ body: JSON.stringify({
+ method: 'leantime.rpc.Tasks.Tasks.getAll',
+ jsonrpc: '2.0',
+ id: 1,
+ params: {
+ userId: session.user.id,
+ status: ['not_started', 'in_progress'],
+ limit: 10,
+ sort: 'dueDate',
+ order: 'ASC'
+ }
+ })
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch tasks from Leantime');
+ }
+
+ const data = await response.json();
+
+ if (!data.result) {
+ return NextResponse.json({ tasks: [] });
+ }
+
+ // Transform the tasks to match our interface
+ const tasks = data.result.map((task: any) => ({
+ id: task.id,
+ headline: task.headline,
+ description: task.description,
+ status: task.status,
+ dueDate: task.dueDate,
+ priority: task.priority
+ }));
+
+ return NextResponse.json({ tasks });
+ } catch (error) {
+ console.error('Error fetching tasks:', error);
+ return NextResponse.json(
+ { error: "Failed to fetch tasks" },
+ { status: 500 }
+ );
+ }
+}
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index 62bc8e55..f6f9ed3f 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,5 +1,6 @@
import { QuoteCard } from "@/components/quote-card";
import { Parole } from "@/components/parole";
+import { Flow } from "@/components/flow";
export const metadata = {
title: "Enkun - Dashboard",
@@ -15,6 +16,9 @@ export default function DashboardPage() {
+
+
+
);
diff --git a/components/flow.tsx b/components/flow.tsx
new file mode 100644
index 00000000..573e6eef
--- /dev/null
+++ b/components/flow.tsx
@@ -0,0 +1,165 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import { RefreshCw, Calendar, CheckCircle2, Clock } from "lucide-react";
+import { useRouter } from "next/navigation";
+import { useSession } from "next-auth/react";
+
+interface Task {
+ id: string;
+ headline: string;
+ description: string;
+ status: string;
+ dueDate: string;
+ priority: number;
+}
+
+export function Flow() {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [refreshing, setRefreshing] = useState(false);
+ const router = useRouter();
+ const { data: session } = useSession();
+
+ const fetchTasks = async (isRefresh = false) => {
+ try {
+ if (isRefresh) {
+ setRefreshing(true);
+ }
+ const response = await fetch('/api/leantime/tasks', {
+ cache: 'no-store',
+ next: { revalidate: 0 },
+ });
+
+ if (response.status === 401) {
+ setError('Session expired. Please sign in again.');
+ return;
+ }
+
+ if (!response.ok) {
+ const errorData = await response.json();
+ throw new Error(errorData.error || 'Failed to fetch tasks');
+ }
+
+ const data = await response.json();
+ setTasks(data.tasks);
+ setError(null);
+ } catch (err) {
+ console.error('Error fetching tasks:', err);
+ const errorMessage = err instanceof Error ? err.message : 'Failed to fetch tasks';
+ setError(errorMessage);
+ } finally {
+ setLoading(false);
+ setRefreshing(false);
+ }
+ };
+
+ useEffect(() => {
+ if (session) {
+ fetchTasks();
+ // Set up polling every 5 minutes
+ const interval = setInterval(() => fetchTasks(), 300000);
+ return () => clearInterval(interval);
+ }
+ }, [session]);
+
+ const getPriorityColor = (priority: number) => {
+ switch (priority) {
+ case 1:
+ return 'text-red-500';
+ case 2:
+ return 'text-yellow-500';
+ case 3:
+ return 'text-green-500';
+ default:
+ return 'text-gray-500';
+ }
+ };
+
+ const getStatusIcon = (status: string) => {
+ switch (status.toLowerCase()) {
+ case 'done':
+ return ;
+ case 'in progress':
+ return ;
+ default:
+ return ;
+ }
+ };
+
+ return (
+ router.push('/flow')}
+ >
+
+ My Tasks
+
+
+
+ {loading && Loading tasks...
}
+ {error && (
+
+
Error: {error}
+
+
+ )}
+ {!loading && !error && (
+
+ {tasks.length === 0 ? (
+
No tasks found
+ ) : (
+ tasks.map((task) => (
+
+
+ {getStatusIcon(task.status)}
+
+
+
+
{task.headline}
+
+ {task.priority === 1 ? 'High' : task.priority === 2 ? 'Medium' : 'Low'}
+
+
+
{task.description}
+ {task.dueDate && (
+
+ Due: {new Date(task.dueDate).toLocaleDateString()}
+
+ )}
+
+
+ ))
+ )}
+
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/parole.tsx b/components/parole.tsx
index 93a390b4..90c66afd 100644
--- a/components/parole.tsx
+++ b/components/parole.tsx
@@ -115,7 +115,7 @@ export function Parole() {
return (
router.push('/parole')}
>
@@ -133,7 +133,7 @@ export function Parole() {
-
+
{loading && Loading messages...
}
{error && (
@@ -151,13 +151,13 @@ export function Parole() {
)}
{!loading && !error && (
-
+
{messages.length === 0 ? (
No messages found
) : (
messages.map((message) => (
-
-
+
+
{(message.u.name || message.u.username).substring(0, 2).toUpperCase()}
diff --git a/components/users/add-user-button.tsx b/components/users/add-user-button.tsx
index 131c5778..d45d8806 100644
--- a/components/users/add-user-button.tsx
+++ b/components/users/add-user-button.tsx
@@ -137,8 +137,15 @@ export function AddUserButton({ userRole, handleAddUser }: AddUserButtonProps) {
Admin
- Enseignant
- Étudiant
+ Apprentices
+ Coding
+ Data Intelligence
+ Entrepreneurship
+ Expression
+ Investigation
+ Lean
+ Mediation
+ Mentors