feat: refactor Daily components and enhance UI integration
- Replaced `DailyCalendar` with a new `Calendar` component for improved functionality and consistency. - Introduced `AlertBanner` to replace `DeadlineReminder`, providing a more flexible way to display urgent tasks. - Updated `DailyAddForm` to use new options for task types, enhancing user experience when adding tasks. - Removed unused state and components, streamlining the DailyPageClient for better performance and maintainability. - Enhanced `DailySection` to utilize new `CheckboxItem` format for better integration with the UI. - Cleaned up imports and improved overall structure for better readability.
This commit is contained in:
224
src/components/ui/CollapsibleSection.tsx
Normal file
224
src/components/ui/CollapsibleSection.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
export interface CollapsibleItem {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
metadata?: string;
|
||||
isChecked?: boolean;
|
||||
isArchived?: boolean;
|
||||
icon?: string;
|
||||
actions?: Array<{
|
||||
label: string;
|
||||
icon: string;
|
||||
onClick: () => void;
|
||||
variant?: 'primary' | 'secondary' | 'destructive';
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
interface CollapsibleSectionProps {
|
||||
title: string;
|
||||
items: CollapsibleItem[];
|
||||
icon?: string;
|
||||
defaultCollapsed?: boolean;
|
||||
loading?: boolean;
|
||||
emptyMessage?: string;
|
||||
filters?: Array<{
|
||||
label: string;
|
||||
value: string;
|
||||
options: Array<{ value: string; label: string }>;
|
||||
onChange: (value: string) => void;
|
||||
}>;
|
||||
onRefresh?: () => void;
|
||||
onItemToggle?: (itemId: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function CollapsibleSection({
|
||||
title,
|
||||
items,
|
||||
icon = '📋',
|
||||
defaultCollapsed = false,
|
||||
loading = false,
|
||||
emptyMessage = 'Aucun élément',
|
||||
filters = [],
|
||||
onRefresh,
|
||||
onItemToggle,
|
||||
className = ''
|
||||
}: CollapsibleSectionProps) {
|
||||
const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed);
|
||||
|
||||
const handleItemToggle = (itemId: string) => {
|
||||
onItemToggle?.(itemId);
|
||||
};
|
||||
|
||||
const getItemClasses = (item: CollapsibleItem) => {
|
||||
let classes = 'flex items-center gap-3 p-3 rounded-lg border border-[var(--border)]';
|
||||
|
||||
if (item.isArchived) {
|
||||
classes += ' opacity-60 bg-[var(--muted)]/20';
|
||||
} else {
|
||||
classes += ' bg-[var(--card)]';
|
||||
}
|
||||
|
||||
return classes;
|
||||
};
|
||||
|
||||
const getCheckboxClasses = (item: CollapsibleItem) => {
|
||||
let classes = 'w-5 h-5 rounded border-2 flex items-center justify-center transition-colors';
|
||||
|
||||
if (item.isArchived) {
|
||||
classes += ' border-[var(--muted)] cursor-not-allowed';
|
||||
} else {
|
||||
classes += ' border-[var(--border)] hover:border-[var(--primary)]';
|
||||
}
|
||||
|
||||
return classes;
|
||||
};
|
||||
|
||||
const getActionClasses = (action: NonNullable<CollapsibleItem['actions']>[0]) => {
|
||||
let classes = 'text-xs px-2 py-1';
|
||||
|
||||
switch (action.variant) {
|
||||
case 'destructive':
|
||||
classes += ' text-[var(--destructive)] hover:text-[var(--destructive)]';
|
||||
break;
|
||||
case 'primary':
|
||||
classes += ' text-[var(--primary)] hover:text-[var(--primary)]';
|
||||
break;
|
||||
default:
|
||||
classes += ' text-[var(--foreground)]';
|
||||
}
|
||||
|
||||
return classes;
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={`mt-6 ${className}`}>
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className="flex items-center gap-2 text-lg font-semibold hover:text-[var(--primary)] transition-colors"
|
||||
>
|
||||
<span className={`transform transition-transform ${isCollapsed ? 'rotate-0' : 'rotate-90'}`}>
|
||||
▶️
|
||||
</span>
|
||||
{icon} {title}
|
||||
{items.length > 0 && (
|
||||
<span className="bg-[var(--warning)] text-[var(--warning-foreground)] px-2 py-1 rounded-full text-xs font-medium">
|
||||
{items.length}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{!isCollapsed && (
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Filtres */}
|
||||
{filters.map((filter, index) => (
|
||||
<select
|
||||
key={index}
|
||||
value={filter.value}
|
||||
onChange={(e) => filter.onChange(e.target.value)}
|
||||
className="text-xs px-2 py-1 border border-[var(--border)] rounded bg-[var(--background)]"
|
||||
>
|
||||
{filter.options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
))}
|
||||
|
||||
{/* Bouton refresh */}
|
||||
{onRefresh && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={onRefresh}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? '🔄' : '↻'}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
{!isCollapsed && (
|
||||
<CardContent>
|
||||
{loading ? (
|
||||
<div className="text-center py-4 text-[var(--muted-foreground)]">
|
||||
Chargement...
|
||||
</div>
|
||||
) : items.length === 0 ? (
|
||||
<div className="text-center py-4 text-[var(--muted-foreground)]">
|
||||
🎉 {emptyMessage} ! Excellent travail.
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{items.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className={getItemClasses(item)}
|
||||
>
|
||||
{/* Checkbox */}
|
||||
{item.isChecked !== undefined && (
|
||||
<button
|
||||
onClick={() => handleItemToggle(item.id)}
|
||||
disabled={item.isArchived}
|
||||
className={getCheckboxClasses(item)}
|
||||
>
|
||||
{item.isChecked && <span className="text-[var(--primary)]">✓</span>}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{/* Contenu */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
{item.icon && <span>{item.icon}</span>}
|
||||
<span className={`text-sm font-medium ${item.isArchived ? 'line-through' : ''}`}>
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
{(item.subtitle || item.metadata) && (
|
||||
<div className="flex items-center gap-3 text-xs text-[var(--muted-foreground)]">
|
||||
{item.subtitle && <span>{item.subtitle}</span>}
|
||||
{item.metadata && <span>{item.metadata}</span>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
{item.actions && (
|
||||
<div className="flex items-center gap-1">
|
||||
{item.actions.map((action, index) => (
|
||||
<Button
|
||||
key={index}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={action.onClick}
|
||||
disabled={action.disabled}
|
||||
title={action.label}
|
||||
className={getActionClasses(action)}
|
||||
>
|
||||
{action.icon}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user