- 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.
225 lines
6.9 KiB
TypeScript
225 lines
6.9 KiB
TypeScript
'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>
|
||
);
|
||
}
|