From 0539d6963ebedd554cd3cd36b6c94df9659d6a6b Mon Sep 17 00:00:00 2001 From: Maksymilian Jopek Date: Mon, 28 Apr 2025 18:03:28 +0200 Subject: Initial commit Simple frontend version with mock API --- src/components/Button.module.css | 29 +++++++ src/components/Button.tsx | 57 +++++++++++++ src/components/Timer.module.css | 63 ++++++++++++++ src/components/Timer.tsx | 178 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 327 insertions(+) create mode 100644 src/components/Button.module.css create mode 100644 src/components/Button.tsx create mode 100644 src/components/Timer.module.css create mode 100644 src/components/Timer.tsx (limited to 'src/components') diff --git a/src/components/Button.module.css b/src/components/Button.module.css new file mode 100644 index 0000000..7041eaf --- /dev/null +++ b/src/components/Button.module.css @@ -0,0 +1,29 @@ +.btn:last-child { + margin-bottom: 0; +} +.generic-button { + display: grid; + place-items: center; + border: 3px solid #222; + color: white; + border-radius: 15px; + font-size: 1.6em; + width: 100%; + transition: transform 0.05s; + user-select: none; +} +.generic-button:enabled:active { + transform: scale(0.8); + box-shadow: inset 0px 0px 5px 0px black; +} +.generic-button:disabled { + filter: grayscale(0.8); +} +.btn { + composes: generic-button; + --page-size-without-margins: ( + 100svh - (var(--rows-count) + 1) * var(--btn-gap) + ); + --btn-height: calc(var(--page-size-without-margins) / var(--rows-count)); + height: var(--btn-height); +} diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..031fc73 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,57 @@ +import styles from "./Button.module.css"; +import { createEffect, createSignal } from "solid-js"; + +enum BtnType { + click = 0, + switch = 1, +} +export type Button = + | { + type: BtnType.click; + name: string; + color?: string; + } + | { + type: BtnType.switch; + name: string; + }; + +export default function Button(props: Button) { + const [value, setValue] = createSignal(false); + const [bgColor, setBgColor] = createSignal(); + + createEffect(() => { + if (props.type === BtnType.click) { + setBgColor(value() ? "pink" : "blue"); + } else { + setBgColor(value() ? "green" : "red"); + } + }); + + function onClick() { + if (props.type === BtnType.click) { + if (value() === true) { + return; + } + setValue(true); + setTimeout(() => { + setValue(false); + }, 3000); + } else { + setValue((old) => !old); + } + } + + return ( + + ); +} diff --git a/src/components/Timer.module.css b/src/components/Timer.module.css new file mode 100644 index 0000000..b748c9e --- /dev/null +++ b/src/components/Timer.module.css @@ -0,0 +1,63 @@ +.cont { + h2 { + margin-top: 0.4em; + margin-bottom: 1rem; + } + input { + width: 2em; + font-size: 1.5em; + padding: 0.3em; + } +} +.custom-option { + display: grid; + align-items: center; + grid-template-columns: 1fr 2fr 1fr; + margin-bottom: 1em; +} +.units { + display: inline-flex; + gap: 1.5em; + span { + font-size: 2em; + } +} +.not-selected-unit { + font-weight: lighter; +} +.selected-unit { + font-weight: bold; + text-decoration: underline; +} +.darkgray { + color: darkgray; +} +.custom-option-btn { + composes: generic-button from "./Button.module.css"; + background-color: green; + height: 2em; +} +.extra-stop-btn { + composes: generic-button from "./Button.module.css"; + background-color: red; + padding: 0.5em; + margin-top: 0.5em; +} + +.time-left { + margin-bottom: 1em; + h2 { + display: inline; + color: lightblue; + } +} + +.options { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} +.option { + composes: generic-button from "./Button.module.css"; + height: calc((100vw - 2rem) / 3 / 1.5); +} diff --git a/src/components/Timer.tsx b/src/components/Timer.tsx new file mode 100644 index 0000000..61a98f5 --- /dev/null +++ b/src/components/Timer.tsx @@ -0,0 +1,178 @@ +import { createSignal, For, Match, Show, Switch } from "solid-js"; +import styles from "./Timer.module.css"; + +export const TIME_UNITS = ["s", "m", "h", "d"] as const; +export type Timer = { + title: string; + options: { + time: number; + unit: (typeof TIME_UNITS)[number]; + }[]; + backgroundColor: string; + onTill: number | null; + secondsLeftText: string; + extraStopActions: { title: string }[]; +}; + +export default function Timer(props: Timer) { + const [onTill, setOnTill] = createSignal(props.onTill); + const [unit, setUnit] = createSignal<(typeof TIME_UNITS)[number]>( + TIME_UNITS[1], + ); + const [formattedTime, setFormattedTime] = createSignal(""); + const [disabled, setDisabled] = createSignal(onTill() !== null); + const [customValue, setCustomValue] = createSignal(""); + + let _intervalId = null; + function toggleAutoUpdate() { + if (_intervalId === null) { + updateTime(); + _intervalId = setInterval(updateTime, 1000); + } else { + clearInterval(_intervalId); + _intervalId = null; + } + } + if (props.onTill !== null) { + toggleAutoUpdate(); + } + function updateTime() { + const s = Math.floor(onTill() - Date.now() / 1000); + const days = Math.floor(s / (24 * 3600)); + const hours = Math.floor((s % (24 * 3600)) / 3600); + const minutes = Math.floor((s % 3600) / 60); + const seconds = s % 60; + + let text = ""; + if (days > 0) { + text += days + "d : "; + } + if (hours > 0 || text.length > 0) { + text += hours + "h : "; + } + if (minutes > 0 || text.length > 0) { + text += minutes + "m : "; + } + if (seconds > 0 || text.length > 0) { + text += seconds + "s"; + } + setFormattedTime(text); + } + function startStopClick() { + const val = customValue(); + if (disabled() === false && val !== "") { + optionClick({ time: val, unit: unit() }); + } else if (disabled() === true) { + setDisabled(false); + setOnTill(null); + toggleAutoUpdate(); + } + } + + function optionClick(option: Timer["options"][number]) { + setDisabled(true); + let newOnTill = Math.floor(Date.now() / 1000); + if (option.unit === "s") { + newOnTill += option.time; + } else if (option.unit === "m") { + newOnTill += option.time * 60; + } else if (option.unit === "h") { + newOnTill += option.time * 3600; + } else { + newOnTill += option.time * 3600 * 24; + } + setOnTill(newOnTill); + toggleAutoUpdate(); + } + + function extraStopClick(act: Timer["extraStopActions"][number]) { + // Some api call for the extra + startStopClick(); + } + return ( +
+

{props.title}

+ +
+ + setCustomValue(e.target.value ? parseFloat(e.target.value) : "") + } + disabled={disabled()} + min="1" + /> +
+ + {(u) => ( + (disabled() ? null : setUnit(u))} + classList={{ + [styles.selectedUnit]: unit() === u, + [styles.notSelectedUnit]: unit() !== u, + [styles.darkgray]: disabled() === true, + }} + > + {u} + + )} + +
+ + {/**/} + + {(act) => ( + + )} + + {/**/} +
+ +
+ + + Wyłączone +

+
+ 0}> + {props.secondsLeftText} +

{formattedTime()}

+
+
+
+ +
+ + {(option) => ( + + )} + +
+
+ ); +} -- cgit v1.3.1