diff --git a/src/components/panels/TimerCard.tsx b/src/components/panels/TimerCard.tsx index f0cea50..a5a2ef6 100644 --- a/src/components/panels/TimerCard.tsx +++ b/src/components/panels/TimerCard.tsx @@ -26,17 +26,20 @@ function parseMmSs(text: string): number | null { interface TimerCardProps { label: string; + onLabelChange?: (label: string) => void; onRemove?: () => void; } -const TimerCard: React.FC = ({ label, onRemove }) => { - const { status, remainingMs, start, reset } = useCountdown(); +const TimerCard: React.FC = ({ label, onLabelChange, onRemove }) => { + const { status, remainingMs, start, pause, resume, reset } = useCountdown(); const [presetMs, setPresetMs] = useState(DEFAULT_PRESET_MS); const [inputText, setInputText] = useState(formatMmSs(DEFAULT_PRESET_MS)); const [inputInvalid, setInputInvalid] = useState(false); const isIdle = status === 'idle'; + const isRunning = status === 'running'; + const isPaused = status === 'paused'; const isFinished = status === 'finished'; const countdownColor = isFinished @@ -68,7 +71,13 @@ const TimerCard: React.FC = ({ label, onRemove }) => { return (
-

{label}

+ onLabelChange?.(e.target.value)} + /> {onRemove && ( - ) : ( + )} + {isRunning && ( + + )} + {isPaused && ( + + )} + {!isIdle && ( @@ -142,11 +162,28 @@ const TimerCard: React.FC = ({ label, onRemove }) => { align-items: center; } - h4 { + .label-input { margin: 0; + padding: 0.15rem 0.3rem; font-size: 0.95rem; font-weight: 600; color: #eaeaea; + background: transparent; + border: 1px solid transparent; + border-radius: 4px; + width: 100%; + min-width: 0; + font-family: inherit; + } + + .label-input:hover { + border-color: #444; + } + + .label-input:focus { + outline: none; + background: #111; + border-color: #0070f3; } .remove-btn { @@ -229,6 +266,14 @@ const TimerCard: React.FC = ({ label, onRemove }) => { button.reset:hover { background: #666; } + + button.pause { + background: #d9a441; + } + + button.pause:hover { + background: #c4923a; + } `}
); diff --git a/src/components/panels/TimerPanel.tsx b/src/components/panels/TimerPanel.tsx index 6151929..48e2be3 100644 --- a/src/components/panels/TimerPanel.tsx +++ b/src/components/panels/TimerPanel.tsx @@ -3,9 +3,17 @@ import React, { useState } from 'react'; import TimerCard from './TimerCard'; -const DEFAULT_TIMER_COUNT = 5; -const MIN_TIMERS = 1; -const MAX_TIMERS = 8; +type TimerGroup = 'orders' | 'other'; + +const DEFAULT_ORDER_COUNT = 3; +const DEFAULT_OTHER_COUNT = 2; +const MIN_TIMERS_PER_GROUP = 1; +const MAX_TIMERS_PER_GROUP = 20; + +const GROUPS: { key: TimerGroup; title: string; addLabel: string }[] = [ + { key: 'orders', title: 'Orders', addLabel: '+ Add Order Timer' }, + { key: 'other', title: 'Cooking', addLabel: '+ Add Timer' }, +]; function smallestFreeId(ids: number[]): number { const used = new Set(ids); @@ -14,46 +22,91 @@ function smallestFreeId(ids: number[]): number { return id; } +const initialOrderIds = Array.from({ length: DEFAULT_ORDER_COUNT }, (_, i) => i + 1); +const initialOtherIds = Array.from( + { length: DEFAULT_OTHER_COUNT }, + (_, i) => DEFAULT_ORDER_COUNT + i + 1, +); +const initialIds = [...initialOrderIds, ...initialOtherIds]; + const TimerPanel: React.FC = () => { - const [timerIds, setTimerIds] = useState( - Array.from({ length: DEFAULT_TIMER_COUNT }, (_, i) => i + 1), + const [timerIds, setTimerIds] = useState(initialIds); + const [groups, setGroups] = useState>(() => ({ + ...Object.fromEntries(initialOrderIds.map((id) => [id, 'orders' as TimerGroup])), + ...Object.fromEntries(initialOtherIds.map((id) => [id, 'other' as TimerGroup])), + })); + const [labels, setLabels] = useState>(() => + Object.fromEntries(initialIds.map((id) => [id, `Timer ${id}`])), ); - const addTimer = () => { + const addTimer = (group: TimerGroup) => { setTimerIds((prev) => { - if (prev.length >= MAX_TIMERS) return prev; + const countInGroup = prev.filter((id) => groups[id] === group).length; + if (countInGroup >= MAX_TIMERS_PER_GROUP) return prev; const id = smallestFreeId(prev); + setLabels((prevLabels) => ({ ...prevLabels, [id]: `Timer ${id}` })); + setGroups((prevGroups) => ({ ...prevGroups, [id]: group })); return [...prev, id].sort((a, b) => a - b); }); }; const removeTimer = (id: number) => { - setTimerIds((prev) => (prev.length > MIN_TIMERS ? prev.filter((t) => t !== id) : prev)); + const group = groups[id]; + const countInGroup = timerIds.filter((t) => groups[t] === group).length; + if (countInGroup <= MIN_TIMERS_PER_GROUP) return; + + setTimerIds((prev) => prev.filter((t) => t !== id)); + setLabels((prev) => { + const next = { ...prev }; + delete next[id]; + return next; + }); + setGroups((prev) => { + const next = { ...prev }; + delete next[id]; + return next; + }); + }; + + const renameTimer = (id: number, label: string) => { + setLabels((prev) => ({ ...prev, [id]: label })); }; return (

Multi-Timer

-
-
- {timerIds.map((id) => ( - MIN_TIMERS ? () => removeTimer(id) : undefined} - /> - ))} -
+ {GROUPS.map(({ key, title, addLabel }) => { + const idsInGroup = timerIds.filter((id) => groups[id] === key); + return ( +
+
+

{title}

+ +
+ +
+ {idsInGroup.map((id) => ( + renameTimer(id, label)} + onRemove={idsInGroup.length > MIN_TIMERS_PER_GROUP ? () => removeTimer(id) : undefined} + /> + ))} +
+
+ ); + })}