feat: implement Moving Motivators feature with session management, real-time event handling, and UI components for enhanced user experience
This commit is contained in:
143
src/components/moving-motivators/InfluenceZone.tsx
Normal file
143
src/components/moving-motivators/InfluenceZone.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user