Files
towercontrol/src/components/ui/CollapsibleSection.tsx
Julien Froidefond d45a04d347 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.
2025-09-29 09:47:13 +02:00

225 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
);
}