All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m57s
118 lines
3.7 KiB
TypeScript
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';
|