Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState } from "react";
import { useState, useRef } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
import { ChevronRight, Calendar, CalendarClock, Lock } from "lucide-react";
Expand Down Expand Up @@ -123,13 +123,41 @@ export function CompetitionPropView({
const isSubmitting =
createForecastAction.isLoading || updateForecastAction.isLoading;

const [isEditingPercent, setIsEditingPercent] = useState(false);
const [percentInputValue, setPercentInputValue] = useState("");
const percentInputRef = useRef<HTMLInputElement>(null);

const hasChanges = localForecast !== prop.user_forecast;
const colors = getProbColor(localForecast);
const percent =
localForecast !== null ? Math.round(localForecast * 100) : null;

const relativeDeadline = getRelativeDeadline(prop.prop_forecasts_due_date);

const handlePercentClick = () => {
if (!isForecastingOpen) return;
setPercentInputValue(percent !== null ? String(percent) : "");
setIsEditingPercent(true);
setTimeout(() => percentInputRef.current?.select(), 0);
};

const commitPercentInput = () => {
setIsEditingPercent(false);
const trimmed = percentInputValue.trim();
if (trimmed === "") return;
const num = Number(trimmed);
if (isNaN(num) || num < 0 || num > 100) return;
setLocalForecast(Math.round(num) / 100);
};

const handlePercentKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
commitPercentInput();
} else if (e.key === "Escape") {
setIsEditingPercent(false);
}
};

const handleBarClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (!isForecastingOpen) return;
const rect = e.currentTarget.getBoundingClientRect();
Expand Down Expand Up @@ -275,14 +303,40 @@ export function CompetitionPropView({
<div className="flex items-center gap-6 mb-6">
{/* Probability display */}
<div
className={`${colors.bg} ${colors.text} rounded-xl w-28 h-24 flex flex-col items-center justify-center shrink-0 transition-colors`}
className={`${colors.bg} ${colors.text} rounded-xl w-28 h-24 flex flex-col items-center justify-center shrink-0 transition-colors ${
isForecastingOpen ? "cursor-pointer" : ""
}`}
onClick={!isEditingPercent ? handlePercentClick : undefined}
title={isForecastingOpen ? "Click to type a value" : undefined}
>
<div className="text-4xl font-bold">
{percent !== null ? `${percent}%` : "—"}
</div>
<div className="text-xs opacity-70">
{percent !== null ? "Your forecast" : "Not set"}
</div>
{isEditingPercent ? (
<>
<div className="flex items-center">
<input
ref={percentInputRef}
type="text"
inputMode="numeric"
value={percentInputValue}
onChange={(e) => setPercentInputValue(e.target.value)}
onBlur={commitPercentInput}
onKeyDown={handlePercentKeyDown}
className="w-16 text-4xl font-bold text-center bg-transparent outline-none border-b-2 border-current"
aria-label="Forecast percentage"
/>
<span className="text-4xl font-bold">%</span>
</div>
<div className="text-xs opacity-70">Your forecast</div>
</>
) : (
<>
<div className="text-4xl font-bold">
{percent !== null ? `${percent}%` : "—"}
</div>
<div className="text-xs opacity-70">
{percent !== null ? "Your forecast" : "Not set"}
</div>
</>
)}
</div>

{/* Slider */}
Expand Down
56 changes: 51 additions & 5 deletions components/forecast-card/editable-forecast-card.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState } from "react";
import { useState, useRef } from "react";
import { PropWithUserForecast } from "@/types/db_types";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
Expand Down Expand Up @@ -96,11 +96,38 @@ export function EditableForecastCard({
const isSubmitting =
createForecastAction.isLoading || updateForecastAction.isLoading;

const [isEditingPercent, setIsEditingPercent] = useState(false);
const [percentInputValue, setPercentInputValue] = useState("");
const percentInputRef = useRef<HTMLInputElement>(null);

const hasChanges = localForecast !== prop.user_forecast;
const colors = getProbColor(localForecast);
const percent =
localForecast !== null ? Math.round(localForecast * 100) : null;

const handlePercentClick = () => {
setPercentInputValue(percent !== null ? String(percent) : "");
setIsEditingPercent(true);
setTimeout(() => percentInputRef.current?.select(), 0);
};

const commitPercentInput = () => {
setIsEditingPercent(false);
const trimmed = percentInputValue.trim();
if (trimmed === "") return;
const num = Number(trimmed);
if (isNaN(num) || num < 0 || num > 100) return;
setLocalForecast(Math.round(num) / 100);
};

const handlePercentKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
commitPercentInput();
} else if (e.key === "Escape") {
setIsEditingPercent(false);
}
};

const handleBarClick = (e: React.MouseEvent<HTMLDivElement>) => {
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left;
Expand Down Expand Up @@ -162,11 +189,30 @@ export function EditableForecastCard({
<div className="flex items-stretch gap-4">
{/* Probability box */}
<div
className={`${colors.bg} ${colors.text} rounded-lg w-20 flex items-center justify-center shrink-0 transition-colors`}
className={`${colors.bg} ${colors.text} rounded-lg w-20 flex items-center justify-center shrink-0 transition-colors cursor-pointer`}
onClick={!isEditingPercent ? handlePercentClick : undefined}
title="Click to type a value"
>
<div className="text-2xl font-bold">
{percent !== null ? `${percent}%` : "—"}
</div>
{isEditingPercent ? (
<div className="flex items-center">
<input
ref={percentInputRef}
type="text"
inputMode="numeric"
value={percentInputValue}
onChange={(e) => setPercentInputValue(e.target.value)}
onBlur={commitPercentInput}
onKeyDown={handlePercentKeyDown}
className="w-12 text-2xl font-bold text-center bg-transparent outline-none border-b-2 border-current"
aria-label="Forecast percentage"
/>
<span className="text-2xl font-bold">%</span>
</div>
) : (
<div className="text-2xl font-bold">
{percent !== null ? `${percent}%` : "—"}
</div>
)}
</div>

<div className="flex-1 min-w-0">
Expand Down
Loading