Add rent total slider and icons
- Introduce a total rent slider to Rent Division puzzle to vary overall budget - Replace agent labels with icons and show names to the left of icons X-Lovable-Edit-ID: edt-606b97af-eba4-41c9-983c-f406baa289b7
This commit is contained in:
commit
a1ae2df9b9
1 changed files with 89 additions and 35 deletions
|
|
@ -2,6 +2,7 @@ import { useState, useMemo } from "react";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Slider } from "@/components/ui/slider";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -9,9 +10,10 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { RefreshCw } from "lucide-react";
|
import { RefreshCw, User, UserRound, Users, UserCheck } from "lucide-react";
|
||||||
|
|
||||||
const AGENTS = ["Alice", "Bob", "Charlie", "Dana"];
|
const AGENTS = ["Alice", "Bob", "Charlie", "Dana"];
|
||||||
|
const AGENT_ICONS = [User, UserRound, Users, UserCheck];
|
||||||
const ROOM_COLORS = [
|
const ROOM_COLORS = [
|
||||||
"hsl(142, 70%, 45%)", // green
|
"hsl(142, 70%, 45%)", // green
|
||||||
"hsl(0, 70%, 50%)", // red
|
"hsl(0, 70%, 50%)", // red
|
||||||
|
|
@ -31,13 +33,17 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Total rent (controlled by slider)
|
||||||
|
const [totalRent, setTotalRent] = useState(20);
|
||||||
|
|
||||||
// Rents for each room
|
// Rents for each room
|
||||||
const [rents, setRents] = useState<number[]>([5, 5, 5, 5]);
|
const [rents, setRents] = useState<number[]>([5, 5, 5, 5]);
|
||||||
|
|
||||||
// Allocation: assignment[agent] = room index (-1 = unassigned)
|
// Allocation: assignment[agent] = room index (-1 = unassigned)
|
||||||
const [assignment, setAssignment] = useState<number[]>([-1, -1, -1, -1]);
|
const [assignment, setAssignment] = useState<number[]>([-1, -1, -1, -1]);
|
||||||
|
|
||||||
const totalRent = useMemo(() => rents.reduce((a, b) => a + b, 0), [rents]);
|
const currentTotal = useMemo(() => rents.reduce((a, b) => a + b, 0), [rents]);
|
||||||
|
const rentDiff = totalRent - currentTotal;
|
||||||
|
|
||||||
// Compute U-matrix: utility[agent][room] = valuation - rent
|
// Compute U-matrix: utility[agent][room] = valuation - rent
|
||||||
const utilities = useMemo(() => {
|
const utilities = useMemo(() => {
|
||||||
|
|
@ -120,10 +126,25 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
|
||||||
|
|
||||||
const resetAll = () => {
|
const resetAll = () => {
|
||||||
randomizeValuations();
|
randomizeValuations();
|
||||||
|
setTotalRent(20);
|
||||||
setRents([5, 5, 5, 5]);
|
setRents([5, 5, 5, 5]);
|
||||||
setAssignment([-1, -1, -1, -1]);
|
setAssignment([-1, -1, -1, -1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTotalRentChange = (value: number[]) => {
|
||||||
|
const newTotal = value[0];
|
||||||
|
setTotalRent(newTotal);
|
||||||
|
// Distribute evenly
|
||||||
|
const base = Math.floor(newTotal / 4);
|
||||||
|
const remainder = newTotal % 4;
|
||||||
|
setRents([
|
||||||
|
base + (remainder > 0 ? 1 : 0),
|
||||||
|
base + (remainder > 1 ? 1 : 0),
|
||||||
|
base + (remainder > 2 ? 1 : 0),
|
||||||
|
base,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
const getAgentColor = (agentIdx: number) => {
|
const getAgentColor = (agentIdx: number) => {
|
||||||
const room = assignment[agentIdx];
|
const room = assignment[agentIdx];
|
||||||
return room === -1 ? "hsl(0, 0%, 60%)" : ROOM_COLORS[room];
|
return room === -1 ? "hsl(0, 0%, 60%)" : ROOM_COLORS[room];
|
||||||
|
|
@ -147,42 +168,75 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<CardTitle className="text-lg">Agents & Rooms</CardTitle>
|
<CardTitle className="text-lg">Agents & Rooms</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="space-y-4">
|
||||||
<div className="flex gap-8 justify-center">
|
{/* Total Rent Slider */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Total Rent</span>
|
||||||
|
<span className="font-medium">${totalRent}</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
value={[totalRent]}
|
||||||
|
onValueChange={handleTotalRentChange}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
{rentDiff !== 0 && (
|
||||||
|
<p className={`text-xs ${rentDiff > 0 ? 'text-amber-600' : 'text-destructive'}`}>
|
||||||
|
{rentDiff > 0 ? `+$${rentDiff} unallocated` : `-$${Math.abs(rentDiff)} over budget`}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-6 justify-center pt-2">
|
||||||
{/* Agents Column */}
|
{/* Agents Column */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium text-muted-foreground text-center mb-2">
|
<div className="text-sm font-medium text-muted-foreground text-center mb-2">
|
||||||
Agents
|
Agents
|
||||||
</div>
|
</div>
|
||||||
{AGENTS.map((name, idx) => (
|
{AGENTS.map((name, idx) => {
|
||||||
|
const Icon = AGENT_ICONS[idx];
|
||||||
|
return (
|
||||||
<div key={idx} className="flex items-center gap-2">
|
<div key={idx} className="flex items-center gap-2">
|
||||||
|
<span
|
||||||
|
className="text-xs font-medium w-14 text-right"
|
||||||
|
style={{ color: getAgentColor(idx) }}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
<div
|
<div
|
||||||
className="w-12 h-12 rounded-lg border-2 flex items-center justify-center text-xs font-medium"
|
className="w-10 h-10 rounded-lg border-2 flex items-center justify-center"
|
||||||
style={{
|
style={{
|
||||||
borderColor: getAgentColor(idx),
|
borderColor: getAgentColor(idx),
|
||||||
backgroundColor: `${getAgentColor(idx)}20`,
|
backgroundColor: `${getAgentColor(idx)}20`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name.slice(0, 2)}
|
<Icon
|
||||||
|
className="w-5 h-5"
|
||||||
|
style={{ color: getAgentColor(idx) }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Select
|
<Select
|
||||||
value={assignment[idx] === -1 ? "none" : String(assignment[idx])}
|
value={assignment[idx] === -1 ? "none" : String(assignment[idx])}
|
||||||
onValueChange={(v) => handleAssignmentChange(idx, v)}
|
onValueChange={(v) => handleAssignmentChange(idx, v)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-24 h-8 text-xs">
|
<SelectTrigger className="w-20 h-8 text-xs">
|
||||||
<SelectValue placeholder="Room" />
|
<SelectValue placeholder="Room" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="none">None</SelectItem>
|
<SelectItem value="none">None</SelectItem>
|
||||||
{[0, 1, 2, 3].map((r) => (
|
{[0, 1, 2, 3].map((r) => (
|
||||||
<SelectItem key={r} value={String(r)}>
|
<SelectItem key={r} value={String(r)}>
|
||||||
Room {r + 1}
|
R{r + 1}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Rooms Column */}
|
{/* Rooms Column */}
|
||||||
|
|
@ -193,7 +247,7 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
|
||||||
{[0, 1, 2, 3].map((roomIdx) => (
|
{[0, 1, 2, 3].map((roomIdx) => (
|
||||||
<div key={roomIdx} className="flex items-center gap-2">
|
<div key={roomIdx} className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className="w-12 h-12 rounded-lg border-2 flex items-center justify-center text-xs font-bold"
|
className="w-10 h-10 rounded-lg border-2 flex items-center justify-center text-xs font-bold"
|
||||||
style={{
|
style={{
|
||||||
borderColor: ROOM_COLORS[roomIdx],
|
borderColor: ROOM_COLORS[roomIdx],
|
||||||
backgroundColor: `${ROOM_COLORS[roomIdx]}30`,
|
backgroundColor: `${ROOM_COLORS[roomIdx]}30`,
|
||||||
|
|
@ -207,7 +261,7 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
|
||||||
type="number"
|
type="number"
|
||||||
value={rents[roomIdx]}
|
value={rents[roomIdx]}
|
||||||
onChange={(e) => handleRentChange(roomIdx, e.target.value)}
|
onChange={(e) => handleRentChange(roomIdx, e.target.value)}
|
||||||
className="w-14 h-8 text-xs text-center"
|
className="w-12 h-8 text-xs text-center"
|
||||||
min={0}
|
min={0}
|
||||||
max={99}
|
max={99}
|
||||||
/>
|
/>
|
||||||
|
|
@ -215,7 +269,7 @@ const RentDivisionPuzzle = ({ onComplete }: RentDivisionPuzzleProps) => {
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div className="text-sm font-medium pt-2 border-t">
|
<div className="text-sm font-medium pt-2 border-t">
|
||||||
Total: ${totalRent}
|
Sum: ${currentTotal}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue