From 0539d6963ebedd554cd3cd36b6c94df9659d6a6b Mon Sep 17 00:00:00 2001
From: Maksymilian Jopek <maks@jopek.eu>
Date: Mon, 28 Apr 2025 18:03:28 +0200
Subject: Initial commit

Simple frontend version with mock API
---
 src/components/Timer.tsx | 178 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 178 insertions(+)
 create mode 100644 src/components/Timer.tsx

(limited to 'src/components/Timer.tsx')

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<number | "">("");
+
+  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 (
+    <div class={styles.cont}>
+      <h2>{props.title}</h2>
+
+      <div class={styles.customOption}>
+        <input
+          type="number"
+          value={customValue()}
+          on:input={(e) =>
+            setCustomValue(e.target.value ? parseFloat(e.target.value) : "")
+          }
+          disabled={disabled()}
+          min="1"
+        />
+        <div class={styles.units}>
+          <For each={TIME_UNITS}>
+            {(u) => (
+              <span
+                on:click={() => (disabled() ? null : setUnit(u))}
+                classList={{
+                  [styles.selectedUnit]: unit() === u,
+                  [styles.notSelectedUnit]: unit() !== u,
+                  [styles.darkgray]: disabled() === true,
+                }}
+              >
+                {u}
+              </span>
+            )}
+          </For>
+        </div>
+        <button
+          class={styles.customOptionBtn}
+          on:click={startStopClick}
+          disabled={
+            disabled()
+              ? false
+              : customValue() === "" || (customValue() as number) <= 0
+          }
+          style={{ "background-color": disabled() ? "red" : "green" }}
+        >
+          {disabled() ? "Wyłącz" : "Włącz"}
+        </button>
+        {/*<Show when={disabled() === true}>*/}
+        <For each={props.extraStopActions}>
+          {(act) => (
+            <button
+              disabled={disabled() === false}
+              on:click={[extraStopClick, act]}
+              class={styles.extraStopBtn}
+            >
+              {act.title}
+            </button>
+          )}
+        </For>
+        {/*</Show>*/}
+      </div>
+
+      <div class={styles.timeLeft}>
+        <Switch>
+          <Match when={onTill() === null}>
+            <span>Wyłączone</span>
+            <h2> </h2>
+          </Match>
+          <Match when={onTill() > 0}>
+            <span>{props.secondsLeftText} </span>
+            <h2>{formattedTime()}</h2>
+          </Match>
+        </Switch>
+      </div>
+
+      <div class={styles.options}>
+        <For each={props.options}>
+          {(option) => (
+            <button
+              class={styles.option}
+              on:click={[optionClick, option]}
+              style={{ "background-color": props.backgroundColor }}
+              disabled={disabled()}
+            >
+              {option.time} {option.unit}
+            </button>
+          )}
+        </For>
+      </div>
+    </div>
+  );
+}
-- 
cgit v1.3.1

