feat: add compact view feature to Kanban components
- Introduced `compactView` prop in `KanbanBoard`, `KanbanColumn`, and `TaskCard` for a streamlined task display. - Updated `KanbanFilters` to include a toggle for compact view, enhancing user experience. - Adjusted rendering logic in `TaskCard` to conditionally display task details based on the compact view state.
This commit is contained in:
@@ -25,9 +25,10 @@ interface KanbanBoardProps {
|
|||||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||||
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
onUpdateStatus?: (taskId: string, newStatus: TaskStatus) => Promise<void>;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
|
compactView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, onUpdateStatus, loading = false }: KanbanBoardProps) {
|
export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, onUpdateStatus, loading = false, compactView = false }: KanbanBoardProps) {
|
||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
const [activeTask, setActiveTask] = useState<Task | null>(null);
|
||||||
|
|
||||||
@@ -157,6 +158,7 @@ export function KanbanBoard({ tasks, onCreateTask, onDeleteTask, onEditTask, onU
|
|||||||
onDeleteTask={onDeleteTask}
|
onDeleteTask={onDeleteTask}
|
||||||
onEditTask={onEditTask}
|
onEditTask={onEditTask}
|
||||||
onUpdateTitle={onUpdateTitle}
|
onUpdateTitle={onUpdateTitle}
|
||||||
|
compactView={compactView}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export function KanbanBoardContainer() {
|
|||||||
onUpdateTitle={handleUpdateTitle}
|
onUpdateTitle={handleUpdateTitle}
|
||||||
onUpdateStatus={handleUpdateStatus}
|
onUpdateStatus={handleUpdateStatus}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
|
compactView={kanbanFilters.compactView}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EditTaskForm
|
<EditTaskForm
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ interface KanbanColumnProps {
|
|||||||
onDeleteTask?: (taskId: string) => Promise<void>;
|
onDeleteTask?: (taskId: string) => Promise<void>;
|
||||||
onEditTask?: (task: Task) => void;
|
onEditTask?: (task: Task) => void;
|
||||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||||
|
compactView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle }: KanbanColumnProps) {
|
export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTask, onEditTask, onUpdateTitle, compactView = false }: KanbanColumnProps) {
|
||||||
const [showQuickAdd, setShowQuickAdd] = useState(false);
|
const [showQuickAdd, setShowQuickAdd] = useState(false);
|
||||||
|
|
||||||
// Configuration de la zone droppable
|
// Configuration de la zone droppable
|
||||||
@@ -125,7 +126,7 @@ export function KanbanColumn({ id, title, color, tasks, onCreateTask, onDeleteTa
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
tasks.map((task) => (
|
tasks.map((task) => (
|
||||||
<TaskCard key={task.id} task={task} onDelete={onDeleteTask} onEdit={onEditTask} onUpdateTitle={onUpdateTitle} />
|
<TaskCard key={task.id} task={task} onDelete={onDeleteTask} onEdit={onEditTask} onUpdateTitle={onUpdateTitle} compactView={compactView} />
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useState } from 'react';
|
|||||||
import { TaskPriority } from '@/lib/types';
|
import { TaskPriority } from '@/lib/types';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { TagDisplay } from '@/components/ui/TagDisplay';
|
|
||||||
import { useTasksContext } from '@/contexts/TasksContext';
|
import { useTasksContext } from '@/contexts/TasksContext';
|
||||||
|
|
||||||
export interface KanbanFilters {
|
export interface KanbanFilters {
|
||||||
@@ -12,6 +11,7 @@ export interface KanbanFilters {
|
|||||||
tags?: string[];
|
tags?: string[];
|
||||||
priorities?: TaskPriority[];
|
priorities?: TaskPriority[];
|
||||||
showCompleted?: boolean;
|
showCompleted?: boolean;
|
||||||
|
compactView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KanbanFiltersProps {
|
interface KanbanFiltersProps {
|
||||||
@@ -51,6 +51,13 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCompactViewToggle = () => {
|
||||||
|
onFiltersChange({
|
||||||
|
...filters,
|
||||||
|
compactView: !filters.compactView
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleClearFilters = () => {
|
const handleClearFilters = () => {
|
||||||
onFiltersChange({});
|
onFiltersChange({});
|
||||||
};
|
};
|
||||||
@@ -79,6 +86,28 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Bouton vue compacte */}
|
||||||
|
<Button
|
||||||
|
variant={filters.compactView ? "primary" : "ghost"}
|
||||||
|
onClick={handleCompactViewToggle}
|
||||||
|
className="flex items-center gap-2"
|
||||||
|
title={filters.compactView ? "Vue détaillée" : "Vue compacte"}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-4 h-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
{filters.compactView ? (
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||||
|
) : (
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
{filters.compactView ? 'Détaillée' : 'Compacte'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
@@ -174,7 +203,7 @@ export function KanbanFilters({ filters, onFiltersChange }: KanbanFiltersProps)
|
|||||||
<div className="space-y-1 text-sm">
|
<div className="space-y-1 text-sm">
|
||||||
{filters.search && (
|
{filters.search && (
|
||||||
<div className="text-slate-300">
|
<div className="text-slate-300">
|
||||||
Recherche: <span className="text-cyan-400">"{filters.search}"</span>
|
Recherche: <span className="text-cyan-400">“{filters.search}”</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{filters.priorities?.length && (
|
{filters.priorities?.length && (
|
||||||
|
|||||||
@@ -13,9 +13,10 @@ interface TaskCardProps {
|
|||||||
onDelete?: (taskId: string) => Promise<void>;
|
onDelete?: (taskId: string) => Promise<void>;
|
||||||
onEdit?: (task: Task) => void;
|
onEdit?: (task: Task) => void;
|
||||||
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
onUpdateTitle?: (taskId: string, newTitle: string) => Promise<void>;
|
||||||
|
compactView?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TaskCard({ task, onDelete, onEdit, onUpdateTitle }: TaskCardProps) {
|
export function TaskCard({ task, onDelete, onEdit, onUpdateTitle, compactView = false }: TaskCardProps) {
|
||||||
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
||||||
const [editTitle, setEditTitle] = useState(task.title);
|
const [editTitle, setEditTitle] = useState(task.title);
|
||||||
const { tags: availableTags } = useTasksContext();
|
const { tags: availableTags } = useTasksContext();
|
||||||
@@ -92,6 +93,84 @@ export function TaskCard({ task, onDelete, onEdit, onUpdateTitle }: TaskCardProp
|
|||||||
const titleWithoutEmojis = task.title.replace(emojiRegex, '').trim();
|
const titleWithoutEmojis = task.title.replace(emojiRegex, '').trim();
|
||||||
|
|
||||||
|
|
||||||
|
// Vue compacte : seulement le titre
|
||||||
|
if (compactView) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
ref={setNodeRef}
|
||||||
|
style={style}
|
||||||
|
className={`p-2 hover:border-cyan-500/30 hover:shadow-lg hover:shadow-cyan-500/10 transition-all duration-300 cursor-pointer group ${
|
||||||
|
isDragging ? 'opacity-50 rotate-3 scale-105' : ''
|
||||||
|
}`}
|
||||||
|
{...attributes}
|
||||||
|
{...(isEditingTitle ? {} : listeners)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{emojis.length > 0 && (
|
||||||
|
<div className="flex gap-1 flex-shrink-0">
|
||||||
|
{emojis.slice(0, 1).map((emoji, index) => (
|
||||||
|
<span key={index} className="text-sm opacity-80">
|
||||||
|
{emoji}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isEditingTitle ? (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={editTitle}
|
||||||
|
onChange={(e) => setEditTitle(e.target.value)}
|
||||||
|
onKeyDown={handleTitleKeyPress}
|
||||||
|
onBlur={handleTitleSave}
|
||||||
|
autoFocus
|
||||||
|
className="flex-1 bg-transparent border-none outline-none text-slate-100 font-mono text-sm font-medium leading-tight"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<h4
|
||||||
|
className="font-mono text-sm font-medium text-slate-100 leading-tight line-clamp-1 flex-1 cursor-pointer hover:text-cyan-300 transition-colors"
|
||||||
|
onClick={handleTitleClick}
|
||||||
|
title={onUpdateTitle ? "Cliquer pour éditer" : undefined}
|
||||||
|
>
|
||||||
|
{titleWithoutEmojis}
|
||||||
|
</h4>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1 flex-shrink-0">
|
||||||
|
{/* Boutons d'action compacts */}
|
||||||
|
{onEdit && (
|
||||||
|
<button
|
||||||
|
onClick={handleEdit}
|
||||||
|
className="opacity-0 group-hover:opacity-100 w-3 h-3 rounded-full bg-blue-900/50 hover:bg-blue-800/80 border border-blue-500/30 hover:border-blue-400/50 flex items-center justify-center transition-all duration-200 text-blue-400 hover:text-blue-300 text-xs"
|
||||||
|
title="Modifier la tâche"
|
||||||
|
>
|
||||||
|
✎
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{onDelete && (
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
className="opacity-0 group-hover:opacity-100 w-3 h-3 rounded-full bg-red-900/50 hover:bg-red-800/80 border border-red-500/30 hover:border-red-400/50 flex items-center justify-center transition-all duration-200 text-red-400 hover:text-red-300 text-xs"
|
||||||
|
title="Supprimer la tâche"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Indicateur de priorité compact */}
|
||||||
|
<div className={`w-1.5 h-1.5 rounded-full ${
|
||||||
|
task.priority === 'high' ? 'bg-red-400' :
|
||||||
|
task.priority === 'medium' ? 'bg-yellow-400' :
|
||||||
|
'bg-slate-500'
|
||||||
|
}`} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue détaillée : version complète
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
|
|||||||
Reference in New Issue
Block a user