Files
workshop-manager/src/components/swot/SwotQuadrant.tsx

186 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { forwardRef, useState, useTransition, useRef, ReactNode } from 'react';
import type { SwotCategory } from '@prisma/client';
import { createSwotItem } from '@/actions/swot';
import { QuadrantHelpPanel } from './QuadrantHelp';
interface SwotQuadrantProps {
category: SwotCategory;
title: string;
icon: string;
sessionId: string;
isDraggingOver: boolean;
children: ReactNode;
}
const categoryStyles: Record<SwotCategory, { bg: string; border: string; text: string }> = {
STRENGTH: {
bg: 'bg-strength-bg',
border: 'border-strength-border',
text: 'text-strength',
},
WEAKNESS: {
bg: 'bg-weakness-bg',
border: 'border-weakness-border',
text: 'text-weakness',
},
OPPORTUNITY: {
bg: 'bg-opportunity-bg',
border: 'border-opportunity-border',
text: 'text-opportunity',
},
THREAT: {
bg: 'bg-threat-bg',
border: 'border-threat-border',
text: 'text-threat',
},
};
export const SwotQuadrant = forwardRef<HTMLDivElement, SwotQuadrantProps>(
({ category, title, icon, sessionId, isDraggingOver, children, ...props }, ref) => {
const [isAdding, setIsAdding] = useState(false);
const [newContent, setNewContent] = useState('');
const [isPending, startTransition] = useTransition();
const [showHelp, setShowHelp] = useState(false);
const isSubmittingRef = useRef(false);
const styles = categoryStyles[category];
async function handleAdd() {
if (isSubmittingRef.current || !newContent.trim()) {
setIsAdding(false);
return;
}
isSubmittingRef.current = true;
startTransition(async () => {
await createSwotItem(sessionId, {
content: newContent.trim(),
category,
});
setNewContent('');
setIsAdding(false);
isSubmittingRef.current = false;
});
}
function handleKeyDown(e: React.KeyboardEvent) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleAdd();
} else if (e.key === 'Escape') {
setIsAdding(false);
setNewContent('');
}
}
return (
<div
ref={ref}
className={`
rounded-xl border-2 p-4 min-h-[250px] transition-colors
${styles.bg} ${styles.border}
${isDraggingOver ? 'ring-2 ring-primary ring-offset-2' : ''}
`}
{...props}
>
{/* Header */}
<div className="mb-3 flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-xl">{icon}</span>
<h3 className={`font-semibold ${styles.text}`}>{title}</h3>
<button
onClick={() => setShowHelp(!showHelp)}
className={`
flex h-5 w-5 items-center justify-center rounded-full
text-xs font-medium transition-all
${
showHelp
? 'bg-foreground/20 text-foreground'
: 'bg-foreground/5 text-muted hover:bg-foreground/10 hover:text-foreground'
}
`}
aria-label="Aide"
aria-expanded={showHelp}
>
{showHelp ? '×' : '?'}
</button>
</div>
<button
onClick={() => setIsAdding(true)}
className={`
rounded-lg p-1.5 transition-colors
hover:bg-white/50 ${styles.text}
`}
aria-label={`Ajouter un item ${title}`}
>
<svg className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 4v16m8-8H4"
/>
</svg>
</button>
</div>
{/* Help Panel */}
<QuadrantHelpPanel category={category} isOpen={showHelp} />
{/* Items */}
<div className="space-y-2">
{children}
{/* Add Form */}
{isAdding && (
<div className="rounded-lg border border-border bg-card p-2 shadow-sm">
<textarea
autoFocus
value={newContent}
onChange={(e) => setNewContent(e.target.value)}
onKeyDown={handleKeyDown}
onBlur={(e) => {
// Don't trigger on blur if clicking on a button
if (!e.currentTarget.contains(e.relatedTarget as Node)) {
handleAdd();
}
}}
placeholder="Décrivez cet élément..."
className="w-full resize-none rounded border-0 bg-transparent p-1 text-sm text-foreground placeholder:text-muted focus:outline-none focus:ring-0"
rows={2}
disabled={isPending}
/>
<div className="mt-1 flex justify-end gap-1">
<button
onClick={() => {
setIsAdding(false);
setNewContent('');
}}
className="rounded px-2 py-1 text-xs text-muted hover:bg-card-hover"
disabled={isPending}
>
Annuler
</button>
<button
onMouseDown={(e) => {
e.preventDefault(); // Prevent blur from textarea
}}
onClick={handleAdd}
disabled={isPending || !newContent.trim()}
className={`rounded px-2 py-1 text-xs font-medium ${styles.text} hover:bg-white/50 disabled:opacity-50`}
>
{isPending ? '...' : 'Ajouter'}
</button>
</div>
</div>
)}
</div>
</div>
);
}
);
SwotQuadrant.displayName = 'SwotQuadrant';