trello_fake/components/project-detail/project-detail-view.tsx

234 lines
7.7 KiB
TypeScript
Raw Normal View History

"use client";
import { useState, useEffect, useCallback } from "react";
import { FolderKanban, RefreshCw, LayoutDashboard, ListTodo, FileText } from "lucide-react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { Card, CardContent } from "@/components/ui/card";
import { ProjectHeader } from "./project-header";
import { ProjectStats } from "./project-stats";
import { ProjectInfo } from "./project-info";
import { TasksSection } from "./tasks-section";
import { Project } from "@/types/project";
import { Task } from "@/types/task";
import { getTasksByProjectId, calculateTaskStats } from "@/lib/tasksService";
interface ProjectDetailViewProps {
project: Project;
}
// Skeleton para loading (exportado para uso externo)
export function ProjectDetailSkeleton() {
return (
<div className="space-y-6 p-6">
{/* Header skeleton */}
<div className="flex items-start gap-4">
<Skeleton className="h-16 w-16 rounded-full" />
<div className="space-y-2">
<Skeleton className="h-8 w-64" />
<Skeleton className="h-4 w-32" />
</div>
</div>
{/* Stats skeleton */}
<div className="grid grid-cols-4 gap-4">
{Array.from({ length: 4 }).map((_, i) => (
<Card key={i}>
<CardContent className="p-4">
<Skeleton className="h-4 w-20 mb-2" />
<Skeleton className="h-8 w-24" />
</CardContent>
</Card>
))}
</div>
{/* Content skeleton */}
<div className="grid grid-cols-3 gap-6">
<div className="col-span-2 space-y-4">
<Skeleton className="h-48 w-full" />
<Skeleton className="h-48 w-full" />
</div>
<div className="space-y-4">
<Skeleton className="h-64 w-full" />
<Skeleton className="h-48 w-full" />
</div>
</div>
</div>
);
}
// Estado de error
function ErrorState({ onRetry }: { onRetry: () => void }) {
return (
<div className="flex flex-col items-center justify-center py-12 text-center">
<div className="p-3 rounded-full bg-destructive/10 mb-4">
<FolderKanban className="h-8 w-8 text-destructive" />
</div>
<h3 className="text-lg font-semibold mb-1">Error al cargar las tareas</h3>
<p className="text-sm text-muted-foreground mb-4 max-w-sm">
No se pudieron cargar las tareas del proyecto. Por favor, intenta de nuevo.
</p>
<Button onClick={onRetry} variant="outline">
<RefreshCw className="h-4 w-4 mr-2" />
Reintentar
</Button>
</div>
);
}
export function ProjectDetailView({ project: initialProject }: ProjectDetailViewProps) {
const [project, setProject] = useState<Project>(initialProject);
const [tasks, setTasks] = useState<Task[]>([]);
const [isLoadingTasks, setIsLoadingTasks] = useState(true);
const [tasksError, setTasksError] = useState<string | null>(null);
// Cargar tareas
const fetchTasks = useCallback(async () => {
setIsLoadingTasks(true);
setTasksError(null);
try {
const projectTasks = await getTasksByProjectId(project.id);
setTasks(projectTasks);
} catch (error) {
console.error("Error fetching tasks:", error);
setTasksError("Error al cargar las tareas");
} finally {
setIsLoadingTasks(false);
}
}, [project.id]);
useEffect(() => {
fetchTasks();
}, [fetchTasks]);
// Handler para cuando se actualiza el proyecto
const handleProjectUpdated = (updatedProject: Project) => {
setProject(updatedProject);
};
// Handler para cuando se crea una tarea
const handleTaskCreated = (newTask: Task) => {
setTasks(prevTasks => [newTask, ...prevTasks]);
};
// Handler para cuando se actualiza una tarea
const handleTaskUpdated = (updatedTask: Task) => {
setTasks(prevTasks =>
prevTasks.map(task => task.id === updatedTask.id ? updatedTask : task)
);
};
// Handler para cuando se elimina una tarea
const handleTaskDeleted = (taskId: number) => {
setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId));
};
// Calcular estadísticas de tareas
const taskStats = calculateTaskStats(tasks);
return (
<div className="min-h-screen bg-background">
{/* Header del proyecto */}
<ProjectHeader
project={project}
onProjectUpdated={handleProjectUpdated}
/>
{/* Contenido principal */}
<div className="p-6 space-y-6">
{/* Stats cards */}
<ProjectStats project={project} taskStats={taskStats} />
{/* Tabs de contenido */}
<Tabs defaultValue="overview" className="space-y-6">
<TabsList>
<TabsTrigger value="overview" className="gap-2">
<LayoutDashboard className="h-4 w-4" />
Resumen
</TabsTrigger>
<TabsTrigger value="tasks" className="gap-2">
<ListTodo className="h-4 w-4" />
Tareas
{tasks.length > 0 && (
<span className="ml-1 px-1.5 py-0.5 text-xs rounded-full bg-muted">
{tasks.length}
</span>
)}
</TabsTrigger>
<TabsTrigger value="details" className="gap-2">
<FileText className="h-4 w-4" />
Detalles
</TabsTrigger>
</TabsList>
{/* Tab: Resumen */}
<TabsContent value="overview" className="space-y-6">
<ProjectInfo project={project} />
{/* Preview de tareas */}
{!isLoadingTasks && tasks.length > 0 && (
<Card>
<CardContent className="pt-6">
<div className="flex items-center justify-between mb-4">
<h3 className="font-semibold flex items-center gap-2">
<ListTodo className="h-4 w-4" />
Tareas recientes
</h3>
<Button variant="ghost" size="sm" asChild>
<a href="#tasks">Ver todas</a>
</Button>
</div>
<TasksSection
tasks={tasks.slice(0, 5)}
projectId={project.id}
onTaskCreated={handleTaskCreated}
onTaskUpdated={handleTaskUpdated}
onTaskDeleted={handleTaskDeleted}
/>
</CardContent>
</Card>
)}
</TabsContent>
{/* Tab: Tareas */}
<TabsContent value="tasks">
{isLoadingTasks ? (
<div className="space-y-4">
<div className="flex items-center gap-2">
<Skeleton className="h-9 w-[250px]" />
<Skeleton className="h-9 w-[130px]" />
<Skeleton className="h-9 w-[130px]" />
</div>
<div className="space-y-2">
{Array.from({ length: 5 }).map((_, i) => (
<Skeleton key={i} className="h-16 w-full" />
))}
</div>
</div>
) : tasksError ? (
<ErrorState onRetry={fetchTasks} />
) : (
<TasksSection
tasks={tasks}
projectId={project.id}
onTaskCreated={handleTaskCreated}
onTaskUpdated={handleTaskUpdated}
onTaskDeleted={handleTaskDeleted}
/>
)}
</TabsContent>
{/* Tab: Detalles */}
<TabsContent value="details">
<ProjectInfo project={project} />
</TabsContent>
</Tabs>
</div>
</div>
);
}