feat: implement Moving Motivators feature with session management, real-time event handling, and UI components for enhanced user experience

This commit is contained in:
Julien Froidefond
2025-11-28 08:40:39 +01:00
parent a5c17e23f6
commit 448cf61e66
26 changed files with 3191 additions and 183 deletions

View File

@@ -0,0 +1,143 @@
'use client';
import type { MotivatorCard as MotivatorCardType } from '@/lib/types';
import { MOTIVATOR_BY_TYPE } from '@/lib/types';
interface InfluenceZoneProps {
cards: MotivatorCardType[];
onInfluenceChange: (cardId: string, influence: number) => void;
canEdit: boolean;
}
export function InfluenceZone({ cards, onInfluenceChange, canEdit }: InfluenceZoneProps) {
// Sort by importance (orderIndex)
const sortedCards = [...cards].sort((a, b) => b.orderIndex - a.orderIndex);
return (
<div className="space-y-4">
{/* Legend */}
<div className="flex justify-center gap-8 text-sm">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500" />
<span className="text-muted">Influence négative</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-gray-400" />
<span className="text-muted">Neutre</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-green-500" />
<span className="text-muted">Influence positive</span>
</div>
</div>
{/* Cards with sliders */}
<div className="space-y-3">
{sortedCards.map((card) => (
<InfluenceSlider
key={card.id}
card={card}
onInfluenceChange={onInfluenceChange}
disabled={!canEdit}
/>
))}
</div>
</div>
);
}
function InfluenceSlider({
card,
onInfluenceChange,
disabled,
}: {
card: MotivatorCardType;
onInfluenceChange: (cardId: string, influence: number) => void;
disabled: boolean;
}) {
const config = MOTIVATOR_BY_TYPE[card.type];
return (
<div
className={`
flex items-center gap-4 p-4 rounded-xl border border-border bg-card
transition-all hover:shadow-md
`}
>
{/* Card info */}
<div className="flex items-center gap-3 w-40 shrink-0">
<div
className="w-10 h-10 rounded-lg flex items-center justify-center text-xl"
style={{ backgroundColor: `${config.color}20` }}
>
{config.icon}
</div>
<div>
<div className="font-medium text-foreground text-sm">{config.name}</div>
<div className="text-xs text-muted">#{card.orderIndex}</div>
</div>
</div>
{/* Influence slider */}
<div className="flex-1 flex items-center gap-4">
<span className="text-xs text-red-500 font-medium w-6 text-right">-3</span>
<div className="flex-1 relative">
{/* Track background */}
<div className="absolute inset-0 h-2 top-1/2 -translate-y-1/2 rounded-full bg-gradient-to-r from-red-500 via-gray-300 to-green-500" />
{/* Slider */}
<input
type="range"
min={-3}
max={3}
value={card.influence}
onChange={(e) => onInfluenceChange(card.id, parseInt(e.target.value))}
disabled={disabled}
className={`
relative w-full h-8 appearance-none bg-transparent cursor-pointer
[&::-webkit-slider-thumb]:appearance-none
[&::-webkit-slider-thumb]:w-6
[&::-webkit-slider-thumb]:h-6
[&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-white
[&::-webkit-slider-thumb]:border-2
[&::-webkit-slider-thumb]:border-foreground
[&::-webkit-slider-thumb]:shadow-md
[&::-webkit-slider-thumb]:cursor-grab
[&::-webkit-slider-thumb]:active:cursor-grabbing
[&::-moz-range-thumb]:w-6
[&::-moz-range-thumb]:h-6
[&::-moz-range-thumb]:rounded-full
[&::-moz-range-thumb]:bg-white
[&::-moz-range-thumb]:border-2
[&::-moz-range-thumb]:border-foreground
[&::-moz-range-thumb]:shadow-md
[&::-moz-range-thumb]:cursor-grab
disabled:cursor-not-allowed
disabled:[&::-webkit-slider-thumb]:cursor-not-allowed
`}
/>
{/* Zero marker */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-1 h-4 bg-foreground/30 rounded-full pointer-events-none" />
</div>
<span className="text-xs text-green-500 font-medium w-6">+3</span>
</div>
{/* Current value */}
<div
className={`
w-12 h-8 rounded-lg flex items-center justify-center font-bold text-sm
${card.influence > 0 ? 'bg-green-500/20 text-green-600' : ''}
${card.influence < 0 ? 'bg-red-500/20 text-red-600' : ''}
${card.influence === 0 ? 'bg-muted/20 text-muted' : ''}
`}
>
{card.influence > 0 ? `+${card.influence}` : card.influence}
</div>
</div>
);
}