132 lines
4.3 KiB
TypeScript
132 lines
4.3 KiB
TypeScript
'use client';
|
|
|
|
import { forwardRef, useState, useTransition } from 'react';
|
|
import type { YearReviewItem } from '@prisma/client';
|
|
import { updateYearReviewItem, deleteYearReviewItem } from '@/actions/year-review';
|
|
import { YEAR_REVIEW_BY_CATEGORY } from '@/lib/types';
|
|
|
|
interface YearReviewCardProps {
|
|
item: YearReviewItem;
|
|
sessionId: string;
|
|
isDragging: boolean;
|
|
}
|
|
|
|
export const YearReviewCard = forwardRef<HTMLDivElement, YearReviewCardProps>(
|
|
({ item, sessionId, isDragging, ...props }, ref) => {
|
|
const [isEditing, setIsEditing] = useState(false);
|
|
const [content, setContent] = useState(item.content);
|
|
const [isPending, startTransition] = useTransition();
|
|
|
|
const config = YEAR_REVIEW_BY_CATEGORY[item.category];
|
|
|
|
async function handleSave() {
|
|
if (content.trim() === item.content) {
|
|
setIsEditing(false);
|
|
return;
|
|
}
|
|
|
|
if (!content.trim()) {
|
|
// If empty, delete
|
|
startTransition(async () => {
|
|
await deleteYearReviewItem(item.id, sessionId);
|
|
});
|
|
return;
|
|
}
|
|
|
|
startTransition(async () => {
|
|
await updateYearReviewItem(item.id, sessionId, { content: content.trim() });
|
|
setIsEditing(false);
|
|
});
|
|
}
|
|
|
|
async function handleDelete() {
|
|
startTransition(async () => {
|
|
await deleteYearReviewItem(item.id, sessionId);
|
|
});
|
|
}
|
|
|
|
function handleKeyDown(e: React.KeyboardEvent) {
|
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
e.preventDefault();
|
|
handleSave();
|
|
} else if (e.key === 'Escape') {
|
|
setContent(item.content);
|
|
setIsEditing(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={ref}
|
|
className={`
|
|
group relative rounded-lg border bg-card p-3 shadow-sm transition-all
|
|
${isDragging ? 'shadow-lg ring-2 ring-primary' : 'border-border'}
|
|
${isPending ? 'opacity-50' : ''}
|
|
`}
|
|
style={{
|
|
borderLeftColor: config.color,
|
|
borderLeftWidth: '3px',
|
|
}}
|
|
{...props}
|
|
>
|
|
{isEditing ? (
|
|
<textarea
|
|
autoFocus
|
|
value={content}
|
|
onChange={(e) => setContent(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
onBlur={handleSave}
|
|
className="w-full resize-none rounded border-0 bg-transparent p-0 text-sm text-foreground focus:outline-none focus:ring-0"
|
|
rows={2}
|
|
disabled={isPending}
|
|
/>
|
|
) : (
|
|
<>
|
|
<p className="text-sm text-foreground whitespace-pre-wrap">{item.content}</p>
|
|
|
|
{/* Actions (visible on hover) */}
|
|
<div className="absolute right-1 top-1 flex gap-0.5 opacity-0 transition-opacity group-hover:opacity-100">
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
setIsEditing(true);
|
|
}}
|
|
className="rounded p-1 text-muted hover:bg-card-hover hover:text-foreground"
|
|
aria-label="Modifier"
|
|
>
|
|
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
<button
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleDelete();
|
|
}}
|
|
className="rounded p-1 text-muted hover:bg-destructive/10 hover:text-destructive"
|
|
aria-label="Supprimer"
|
|
>
|
|
<svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
YearReviewCard.displayName = 'YearReviewCard';
|