Files
workshop-manager/src/components/weekly-checkin/WeeklyCheckInSection.tsx
Froidefond Julien db7a0cef96
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m57s
refactor(ui): unify low-level controls and expand design system
2026-03-03 15:50:15 +01:00

118 lines
3.7 KiB
TypeScript

'use client';
import { forwardRef, useState, useTransition, useRef, ReactNode } from 'react';
import type { WeeklyCheckInCategory } from '@prisma/client';
import { createWeeklyCheckInItem } from '@/actions/weekly-checkin';
import { WEEKLY_CHECK_IN_BY_CATEGORY, EMOTION_BY_TYPE } from '@/lib/types';
import { IconPlus, InlineAddItem } from '@/components/ui';
import { Select } from '@/components/ui/Select';
interface WeeklyCheckInSectionProps {
category: WeeklyCheckInCategory;
sessionId: string;
isDraggingOver: boolean;
children: ReactNode;
}
export const WeeklyCheckInSection = forwardRef<HTMLDivElement, WeeklyCheckInSectionProps>(
({ category, sessionId, isDraggingOver, children, ...props }, ref) => {
const [isAdding, setIsAdding] = useState(false);
const [newContent, setNewContent] = useState('');
const [newEmotion, setNewEmotion] = useState<'NONE'>('NONE');
const [isPending, startTransition] = useTransition();
const isSubmittingRef = useRef(false);
const config = WEEKLY_CHECK_IN_BY_CATEGORY[category];
async function handleAdd() {
if (isSubmittingRef.current || !newContent.trim()) {
setIsAdding(false);
return;
}
isSubmittingRef.current = true;
startTransition(async () => {
await createWeeklyCheckInItem(sessionId, {
content: newContent.trim(),
category,
emotion: newEmotion,
});
setNewContent('');
setNewEmotion('NONE');
setIsAdding(false);
isSubmittingRef.current = false;
});
}
return (
<div
ref={ref}
className={`
rounded-xl border-2 p-4 min-h-[200px] transition-colors
bg-card border-border
${isDraggingOver ? 'ring-2 ring-primary ring-offset-2' : ''}
`}
style={{
borderLeftColor: config.color,
borderLeftWidth: '4px',
}}
{...props}
>
{/* Header */}
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xl">{config.icon}</span>
<div>
<h3 className="font-semibold text-foreground">{config.title}</h3>
<p className="text-xs text-muted">{config.description}</p>
</div>
</div>
<button
onClick={() => setIsAdding(true)}
className="rounded-lg p-1.5 transition-colors hover:bg-card-hover text-muted hover:text-foreground"
aria-label={`Ajouter un item ${config.title}`}
>
<IconPlus />
</button>
</div>
{/* Items */}
<div className="space-y-2">
{children}
{/* Add Form */}
{isAdding && (
<InlineAddItem
value={newContent}
onChange={setNewContent}
onSubmit={handleAdd}
onCancel={() => {
setIsAdding(false);
setNewContent('');
setNewEmotion('NONE');
}}
isPending={isPending}
placeholder={`Décrivez ${config.title.toLowerCase()}...`}
extra={
<div className="mt-2">
<Select
value={newEmotion}
onChange={(e) => setNewEmotion(e.target.value as typeof newEmotion)}
className="text-xs"
options={Object.values(EMOTION_BY_TYPE).map((em) => ({
value: em.emotion,
label: `${em.icon} ${em.label}`,
}))}
/>
</div>
}
/>
)}
</div>
</div>
);
}
);
WeeklyCheckInSection.displayName = 'WeeklyCheckInSection';