11"use client" ;
22
3+ import { useState } from "react" ;
34import { motion , AnimatePresence } from "framer-motion" ;
45import { useLocale } from "@/features/i18n" ;
5- import { formatTimeLocale , formatRemaining } from "../utils/time" ;
6+ import {
7+ formatTimeLocale ,
8+ formatRemaining ,
9+ formatTargetDisplay ,
10+ parseFutureDatetimeLocal ,
11+ toDatetimeLocalValue ,
12+ } from "../utils/time" ;
613import type { CountdownSession } from "../types" ;
714
815interface SessionListProps {
@@ -11,6 +18,7 @@ interface SessionListProps {
1118 selectedSessionId : string | null ;
1219 onSelectSession : ( id : string ) => void ;
1320 onCancelSession : ( sessionId : string ) => void ;
21+ onEditSession : ( id : string , target : Date ) => void ;
1422 onClearHistory : ( ) => void ;
1523 onRemoveSession : ( id : string ) => void ;
1624 onAddTimer : ( ) => void ;
@@ -28,11 +36,15 @@ export function SessionList({
2836 selectedSessionId,
2937 onSelectSession,
3038 onCancelSession,
39+ onEditSession,
3140 onClearHistory,
3241 onRemoveSession,
3342 onAddTimer,
3443} : SessionListProps ) {
3544 const { t, locale } = useLocale ( ) ;
45+ const [ editingId , setEditingId ] = useState < string | null > ( null ) ;
46+ const [ editValue , setEditValue ] = useState ( "" ) ;
47+ const [ editError , setEditError ] = useState < string | null > ( null ) ;
3648
3749 const historySessions = sessions . filter ( ( s ) => s . status !== "active" ) ;
3850 const hasHistory = historySessions . length > 0 ;
@@ -51,24 +63,24 @@ export function SessionList({
5163 }
5264
5365 return (
54- < div className = "flex flex-col gap-4 " >
66+ < div className = "flex flex-col gap-5 " >
5567 < div className = "flex items-center justify-between" >
56- < h3 className = "text-sm font-semibold uppercase tracking-wider text-slate-400" >
68+ < h3 className = "text-xs font-semibold uppercase tracking-wider text-slate-400" >
5769 { t . sessions . title }
5870 </ h3 >
5971 { activeSessions . length > 0 && (
6072 < button
6173 type = "button"
6274 onClick = { onAddTimer }
63- className = "text-xs font-medium text-blue-400 hover:text-blue-300 transition-colors"
75+ className = "rounded-full border border-white/15 bg-white/5 px-3 py-1.5 text-xs font-medium text-slate-200 transition-colors hover:bg-white/10 "
6476 >
6577 + { t . sessions . addTimer }
6678 </ button >
6779 ) }
6880 </ div >
6981
7082 { activeSessions . length > 0 && (
71- < div className = "flex flex-col gap-2" >
83+ < div className = "flex flex-col gap-2.5 " >
7284 < span className = "text-[10px] uppercase tracking-wider text-slate-500" >
7385 { t . sessions . active }
7486 </ span >
@@ -77,6 +89,7 @@ export function SessionList({
7789 const target = new Date ( session . targetTime ) ;
7890 const remaining = Math . max ( 0 , target . getTime ( ) - Date . now ( ) ) ;
7991 const isSelected = session . id === selectedSessionId ;
92+ const isEditing = editingId === session . id ;
8093 return (
8194 < motion . div
8295 key = { session . id }
@@ -85,33 +98,94 @@ export function SessionList({
8598 animate = { { opacity : 1 , y : 0 } }
8699 exit = { { opacity : 0 , x : - 12 } }
87100 onClick = { ( ) => onSelectSession ( session . id ) }
88- className = { `flex items-center justify-between rounded-xl px-4 py-3 border cursor-pointer transition-colors
89- ${ isSelected ? "bg-blue-500/20 border-blue-500/40" : "bg-white/[0.03] border-white/5 hover:bg-white/[0.06]" } ` }
101+ className = { `rounded-2xl border p-4 cursor-pointer transition-colors ${
102+ isSelected ? "bg-blue-500/20 border-blue-500/40" : "bg-white/[0.03] border-white/10 hover:bg-white/[0.06]"
103+ } `}
90104 >
91- < div className = "flex flex-col gap-0.5 min-w-0 " >
92- < span className = "text-sm font-medium text-slate-200 truncate " >
93- { formatTimeLocale ( target , locale ) }
94- </ span >
95- < span className = "text-xs text-slate-500 tabular-nums" >
96- { formatRemaining ( remaining ) } left
97- </ span >
98- </ div >
99- < div className = "flex items-center gap-2 shrink-0" >
105+ < div className = "flex items-start justify-between gap-3 " >
106+ < div className = "min-w-0 " >
107+ < span className = "block text-base font-semibold text-slate-100 truncate" >
108+ { formatTargetDisplay ( target , locale ) }
109+ </ span >
110+ < span className = "text-xs text-slate-400 tabular-nums" >
111+ { formatRemaining ( remaining ) } left
112+ </ span >
113+ </ div >
100114 < span className = { `rounded-full border px-2 py-0.5 text-[10px] font-medium uppercase ${ STATUS_STYLES . active } ` } >
101- { isSelected ? "•" : "" } { t . sessions . active }
115+ { isSelected ? "• " : "" }
116+ { t . sessions . active }
102117 </ span >
118+ </ div >
119+
120+ < div className = "mt-3 flex flex-wrap items-center gap-2" >
121+ < button
122+ type = "button"
123+ onClick = { ( e ) => {
124+ e . stopPropagation ( ) ;
125+ setEditingId ( session . id ) ;
126+ setEditValue ( toDatetimeLocalValue ( target ) ) ;
127+ setEditError ( null ) ;
128+ } }
129+ className = "rounded-lg border border-white/15 bg-white/5 px-3 py-1.5 text-xs font-medium text-slate-200 hover:bg-white/10"
130+ >
131+ { t . sessions . edit }
132+ </ button >
103133 < button
104134 type = "button"
105135 onClick = { ( e ) => {
106136 e . stopPropagation ( ) ;
107137 onCancelSession ( session . id ) ;
108138 } }
109- className = "rounded-lg px-2 py-1 text-[10px] font-medium text-red-400 hover:bg-red-500/20 transition-colors"
139+ className = "rounded-lg border border-red-500/30 bg-red-500/10 px-3 py-1.5 text-xs font-medium text-red-300 hover:bg-red-500/20 transition-colors"
110140 aria-label = { t . timer . cancel }
111141 >
112142 { t . timer . cancel }
113143 </ button >
114144 </ div >
145+
146+ { isEditing && (
147+ < div className = "mt-3 rounded-xl border border-white/10 bg-black/20 p-3" onClick = { ( e ) => e . stopPropagation ( ) } >
148+ < label className = "mb-2 block text-[11px] uppercase tracking-wider text-slate-400" > { t . sessions . editTarget } </ label >
149+ < input
150+ type = "datetime-local"
151+ value = { editValue }
152+ min = { toDatetimeLocalValue ( new Date ( ) ) }
153+ onChange = { ( e ) => {
154+ setEditValue ( e . target . value ) ;
155+ setEditError ( null ) ;
156+ } }
157+ className = "w-full rounded-lg border border-white/15 bg-white/5 px-3 py-2 text-sm text-slate-100 outline-none focus:border-blue-500/50"
158+ />
159+ { editError && < p className = "mt-2 text-xs text-amber-400" > { editError } </ p > }
160+ < div className = "mt-3 flex items-center gap-2" >
161+ < button
162+ type = "button"
163+ onClick = { ( ) => {
164+ const parsed = parseFutureDatetimeLocal ( editValue ) ;
165+ if ( ! parsed ) {
166+ setEditError ( t . timer . errorPast ) ;
167+ return ;
168+ }
169+ onEditSession ( session . id , parsed ) ;
170+ setEditingId ( null ) ;
171+ } }
172+ className = "rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-blue-500"
173+ >
174+ { t . sessions . save }
175+ </ button >
176+ < button
177+ type = "button"
178+ onClick = { ( ) => {
179+ setEditingId ( null ) ;
180+ setEditError ( null ) ;
181+ } }
182+ className = "rounded-lg border border-white/15 px-3 py-1.5 text-xs text-slate-300 hover:bg-white/10"
183+ >
184+ { t . settings . close }
185+ </ button >
186+ </ div >
187+ </ div >
188+ ) }
115189 </ motion . div >
116190 ) ;
117191 } ) }
@@ -120,7 +194,7 @@ export function SessionList({
120194 ) }
121195
122196 { hasHistory && (
123- < div className = "flex flex-col gap-2" >
197+ < div className = "flex flex-col gap-2.5 " >
124198 < div className = "flex items-center justify-between" >
125199 < span className = "text-[10px] uppercase tracking-wider text-slate-500" >
126200 { t . sessions . completed } / { t . sessions . cancelled }
0 commit comments