feat: 세션 중, 끝났을 때 목표 표시

This commit is contained in:
2026-02-05 12:35:35 +09:00
parent 61cdba9822
commit b5dd39c6c2
5 changed files with 74 additions and 16 deletions

View File

@@ -54,7 +54,7 @@
--card-foreground: oklch(0.129 0.042 264.695); --card-foreground: oklch(0.129 0.042 264.695);
--popover: oklch(1 0 0); --popover: oklch(1 0 0);
--popover-foreground: oklch(15.152% 0.01301 277.362); --popover-foreground: oklch(15.152% 0.01301 277.362);
--primary: oklch(0.208 0.042 265.755); --primary: oklch(0.574 0.202 262);
--primary-foreground: oklch(0.984 0.003 247.858); --primary-foreground: oklch(0.984 0.003 247.858);
--secondary: oklch(0.968 0.007 247.896); --secondary: oklch(0.968 0.007 247.896);
--secondary-foreground: oklch(0.208 0.042 265.755); --secondary-foreground: oklch(0.208 0.042 265.755);
@@ -65,7 +65,7 @@
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.929 0.013 255.508); --border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508); --input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788); --ring: oklch(0.62 0.202 262);
--chart-1: oklch(0.646 0.222 41.116); --chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704); --chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392); --chart-3: oklch(0.398 0.07 227.392);

View File

@@ -49,7 +49,7 @@ export default function HomePage() {
params.set("mode", mode); params.set("mode", mode);
if (goal && goal.trim().length > 0) { if (goal && goal.trim().length > 0) {
localStorage.setItem("goal", goal.trim()); localStorage.setItem("hushroom:session-goal", goal.trim());
} }
router.push(`/session?${params.toString()}`); router.push(`/session?${params.toString()}`);
@@ -197,7 +197,7 @@ function SessionGoalDialog({
value={goal} value={goal}
onChange={(e) => setGoal(e.target.value)} onChange={(e) => setGoal(e.target.value)}
placeholder="목표를 입력하세요" placeholder="목표를 입력하세요"
className="text-lg border-[#2F6FED] focus-visible:ring-[#2F6FED] focus-visible:ring-1" className="text-lg"
autoFocus autoFocus
/> />
<div className="text-xs text-slate-500"> <div className="text-xs text-slate-500">
@@ -209,11 +209,7 @@ function SessionGoalDialog({
<Button variant="ghost" onClick={() => onOpenChange(false)}> <Button variant="ghost" onClick={() => onOpenChange(false)}>
</Button> </Button>
<Button <Button onClick={onStart} className="rounded-xl" disabled={!mode}>
onClick={onStart}
className="rounded-xl bg-[#2F6FED]"
disabled={!mode}
>
</Button> </Button>
</DialogFooter> </DialogFooter>

View File

@@ -2,7 +2,7 @@
"use client"; "use client";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { useMemo } from "react"; import { useMemo, useState } from "react";
type Mode = "freeflow" | "sprint" | "deepwork"; type Mode = "freeflow" | "sprint" | "deepwork";
@@ -34,11 +34,17 @@ export default function SessionEndPage() {
const router = useRouter(); const router = useRouter();
const params = useSearchParams(); const params = useSearchParams();
const mode = useMemo(() => clampMode(params.get("mode")), [params]); const [elapsed, setElapsed] = useState(() => {
const elapsed = useMemo(() => { const v = Number(localStorage.getItem("hushroom:session-elapsed") ?? "0");
const v = Number(params.get("elapsed") ?? "0");
return Number.isFinite(v) ? v : 0; return Number.isFinite(v) ? v : 0;
}, [params]); });
const [goal, setGoal] = useState(() => {
const goal = localStorage.getItem("hushroom:session-goal");
return goal;
});
const mode = useMemo(() => clampMode(params.get("mode")), [params]);
return ( return (
<main className="min-h-screen w-full" style={{ backgroundColor: BG }}> <main className="min-h-screen w-full" style={{ backgroundColor: BG }}>
@@ -59,6 +65,22 @@ export default function SessionEndPage() {
<div className="mt-3 text-[44px] font-semibold leading-none text-slate-900 tabular-nums"> <div className="mt-3 text-[44px] font-semibold leading-none text-slate-900 tabular-nums">
{hhmmss(elapsed)} {hhmmss(elapsed)}
</div> </div>
{/* Goal */}
{goal && (
<div
className="mt-4 rounded-2xl border bg-[#F1F5FF] px-4 py-3"
style={{ borderColor: BORDER }}
>
<div className="text-xs font-semibold text-slate-600">
</div>
<div className="mt-1 text-xl font-semibold text-slate-900 line-clamp-2">
{goal}
</div>
</div>
)}
<div className="mt-3 text-base text-slate-700"> </div> <div className="mt-3 text-base text-slate-700"> </div>
</div> </div>

View File

@@ -293,13 +293,20 @@ export default function SessionPage() {
} }
}, [elapsed, mode]); }, [elapsed, mode]);
useEffect(() => {
const goal = localStorage.getItem("hushroom:session-goal") ?? "";
setGoal(goal);
}, []);
const timeMain = useMemo(() => { const timeMain = useMemo(() => {
if (mode === "freeflow") return formatHHMMSS(elapsed); if (mode === "freeflow") return formatHHMMSS(elapsed);
return formatHHMMSS(remaining); return formatHHMMSS(remaining);
}, [elapsed, remaining, mode]); }, [elapsed, remaining, mode]);
const onEnd = () => const onEnd = () => {
router.push(`/session/end?mode=${mode}&elapsed=${elapsed}`); localStorage.setItem("hushroom:session-elapsed", elapsed.toString());
router.push(`/session/end?mode=${mode}`);
};
return ( return (
<main className="min-h-screen w-full" style={{ backgroundColor: BG }}> <main className="min-h-screen w-full" style={{ backgroundColor: BG }}>
@@ -330,6 +337,21 @@ export default function SessionPage() {
? "원할 때 종료" ? "원할 때 종료"
: "한 번 실행되고 끝나면 요약으로 이동"} : "한 번 실행되고 끝나면 요약으로 이동"}
</div> </div>
{/* Goal */}
{goal && (
<div
className="mt-4 rounded-2xl border bg-[#F1F5FF] px-4 py-3"
style={{ borderColor: BORDER }}
>
<div className="text-xs font-semibold text-slate-600">
</div>
<div className="mt-1 text-xl font-semibold text-slate-900 line-clamp-2">
{goal}
</div>
</div>
)}
</div> </div>
<div className="text-[44px] font-semibold leading-none text-slate-900 tabular-nums"> <div className="text-[44px] font-semibold leading-none text-slate-900 tabular-nums">
{timeMain} {timeMain}

18
src/components/Modal.tsx Normal file
View File

@@ -0,0 +1,18 @@
// import { ReactNode } from "react";
// interface ModalProps {
// open: boolean;
// onClose: () => void;
// children: ReactNode;
// }
// export default function Modal({ open, onClose, children }: ModalProps) {
// return (
// <div
// onClick={onClose}
// className={`flex fixed inset-0 justify-center items-center transition-colors ${open ? "visible bg-black/20" : "invisible"}`}
// >
// {children}
// </div>
// );
// }