Retour au catalogue
Dashboard Widget Kanban
Tableau kanban interactif avec colonnes de statut, cartes draggables et indicateurs de priorite.
dashboard-widgetscomplex Both Responsive a11y
minimalcorporatesaassaasagencygrid
Theme
"use client";
import { useState, useRef } from "react";
import { motion, Reorder, AnimatePresence, useInView } from "framer-motion";
import {
Plus,
MoreHorizontal,
Clock,
AlertCircle,
CheckCircle2,
Circle,
Tag,
} from "lucide-react";
interface KanbanCard {
id: string;
title: string;
assignee: string;
priority: "low" | "medium" | "high";
tags: string[];
dueDate?: string;
}
interface KanbanColumn {
id: string;
title: string;
color: string;
cards: KanbanCard[];
}
interface DashboardWidgetKanbanProps {
columns?: KanbanColumn[];
title?: string;
}
const priorityConfig = {
low: { icon: Circle, color: "#10B981", label: "Basse" },
medium: { icon: Clock, color: "#F59E0B", label: "Moyenne" },
high: { icon: AlertCircle, color: "#EF4444", label: "Haute" },
};
const ease: [number, number, number, number] = [0.16, 1, 0.3, 1];
export default function DashboardWidgetKanban({
columns = [],
title = "Tableau des taches",
}: DashboardWidgetKanbanProps) {
const sectionRef = useRef<HTMLDivElement>(null);
const isInView = useInView(sectionRef, { once: true, amount: 0.2 });
const [boardColumns, setBoardColumns] = useState(columns);
return (
<section ref={sectionRef} className="py-12 px-6">
<div className="max-w-6xl mx-auto">
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.5, ease }}
className="flex items-center justify-between mb-8"
>
<h2
className="text-xl font-bold"
style={{ color: "var(--color-foreground)" }}
>
{title}
</h2>
<div className="flex items-center gap-2">
<span
className="text-xs"
style={{ color: "var(--color-foreground-muted)" }}
>
{boardColumns.reduce((sum, col) => sum + col.cards.length, 0)} taches
</span>
<button
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium"
style={{
background: "var(--color-accent)",
color: "var(--color-background)",
}}
>
<Plus size={14} />
Ajouter
</button>
</div>
</motion.div>
<div className="flex gap-4 overflow-x-auto pb-4">
{boardColumns.map((column, ci) => (
<motion.div
key={column.id}
initial={{ opacity: 0, y: 20 }}
animate={isInView ? { opacity: 1, y: 0 } : {}}
transition={{ duration: 0.4, delay: ci * 0.1, ease }}
className="w-72 shrink-0 rounded-2xl p-3 flex flex-col"
style={{
background: "var(--color-background-card)",
border: "1px solid var(--color-border)",
}}
>
{/* Column header */}
<div className="flex items-center justify-between mb-3 px-1">
<div className="flex items-center gap-2">
<div
className="w-3 h-3 rounded-full"
style={{ background: column.color }}
/>
<span
className="text-sm font-semibold"
style={{ color: "var(--color-foreground)" }}
>
{column.title}
</span>
<span
className="text-[10px] px-1.5 py-0.5 rounded-full font-medium"
style={{
background: "var(--color-background-alt)",
color: "var(--color-foreground-muted)",
}}
>
{column.cards.length}
</span>
</div>
<button
style={{ color: "var(--color-foreground-light)" }}
>
<MoreHorizontal size={16} />
</button>
</div>
{/* Cards */}
<div className="flex flex-col gap-2 flex-1 min-h-[200px]">
{column.cards.map((card, cardIdx) => {
const pConfig = priorityConfig[card.priority];
const PriorityIcon = pConfig.icon;
return (
<motion.div
key={card.id}
layout
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{
duration: 0.3,
delay: ci * 0.1 + cardIdx * 0.05,
}}
className="p-3 rounded-xl cursor-grab active:cursor-grabbing"
style={{
background: "var(--color-background)",
border: "1px solid var(--color-border)",
}}
whileHover={{
y: -2,
boxShadow: "0 8px 20px -8px rgba(0,0,0,0.12)",
}}
>
{/* Priority */}
<div className="flex items-center justify-between mb-2">
<div
className="flex items-center gap-1 text-[10px] font-medium"
style={{ color: pConfig.color }}
>
<PriorityIcon size={12} />
{pConfig.label}
</div>
<MoreHorizontal
size={14}
style={{
color: "var(--color-foreground-light)",
}}
/>
</div>
{/* Title */}
<h4
className="text-sm font-medium mb-2"
style={{ color: "var(--color-foreground)" }}
>
{card.title}
</h4>
{/* Tags */}
<div className="flex flex-wrap gap-1 mb-3">
{card.tags.map((tag) => (
<span
key={tag}
className="text-[10px] px-2 py-0.5 rounded-full"
style={{
background: "var(--color-accent-subtle)",
color: "var(--color-accent)",
}}
>
{tag}
</span>
))}
</div>
{/* Footer */}
<div className="flex items-center justify-between">
<div
className="w-6 h-6 rounded-full flex items-center justify-center text-[9px] font-bold"
style={{
background: "var(--color-background-alt)",
color: "var(--color-foreground-muted)",
border: "1px solid var(--color-border)",
}}
>
{card.assignee.charAt(0)}
</div>
{card.dueDate && (
<div
className="flex items-center gap-1 text-[10px]"
style={{
color: "var(--color-foreground-light)",
}}
>
<Clock size={10} />
{card.dueDate}
</div>
)}
</div>
</motion.div>
);
})}
{/* Add card button */}
<button
className="w-full py-2.5 rounded-xl text-xs font-medium flex items-center justify-center gap-1 mt-1 transition-colors"
style={{
border: "1px dashed var(--color-border)",
color: "var(--color-foreground-light)",
}}
>
<Plus size={14} />
Ajouter une tache
</button>
</div>
</motion.div>
))}
</div>
</div>
</section>
);
}