commit c875ab428e09992c5d010aee3b0ac2a2b640dfed
parent 346f2d8cfd4248524f41b2ad93301c98c91b5834
Author: Sebastian <sebasjm@taler-systems.com>
Date: Wed, 25 Mar 2026 16:13:00 -0300
fix input duration
Diffstat:
1 file changed, 55 insertions(+), 24 deletions(-)
diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDurationDropdown.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDurationDropdown.tsx
@@ -26,7 +26,7 @@ import {
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, h, VNode } from "preact";
-import { useState } from "preact/hooks";
+import { useEffect, useState } from "preact/hooks";
import { Tooltip } from "../Tooltip.js";
import { InternalTextInputSwitch } from "./Input.js";
import { InputProps, useField } from "./useField.js";
@@ -45,11 +45,13 @@ export interface Props<T> extends InputProps<T> {
function defaultToInputNumberString(
d: Duration | undefined,
- unit: PosibleUnits,
+ unit: PosibleUnits | undefined,
): string | undefined {
- if (!d) return undefined;
- if (d.d_ms === "forever") return undefined;
+ if (!d) return "";
+ if (d.d_ms === "forever") return "Forever";
switch (unit) {
+ case undefined:
+ return "";
case "days":
return String(Math.trunc(d.d_ms / (1000 * 60 * 60 * 24)));
case "hours":
@@ -58,19 +60,23 @@ function defaultToInputNumberString(
return String(Math.trunc(d.d_ms / (1000 * 60)));
case "seconds":
return String(Math.trunc(d.d_ms / 1000));
+ case "forever":
+ return "";
default:
assertUnreachable(unit);
}
}
function defaultFromInputToDuration(
value: string | undefined,
- unit: PosibleUnits,
+ unit: PosibleUnits | undefined,
): Duration | undefined {
if (!value) return undefined;
const num = Number.parseInt(value, 10);
if (Number.isNaN(num)) return undefined;
switch (unit) {
+ case undefined:
+ return undefined;
case "days":
return Duration.fromSpec({ days: num });
case "hours":
@@ -79,24 +85,26 @@ function defaultFromInputToDuration(
return Duration.fromSpec({ minutes: num });
case "seconds":
return Duration.fromSpec({ seconds: num });
+ case "forever":
+ return { d_ms: "forever" };
default:
assertUnreachable(unit);
}
}
-const UNIT_VALUES = ["days", "hours", "minutes", "seconds"] as const;
+const UNIT_VALUES = ["forever", "days", "hours", "minutes", "seconds"] as const;
type PosibleUnits = (typeof UNIT_VALUES)[number];
function isDivisible(v: number, size: number) {
return v / size - Math.trunc(v / size) === 0;
}
-function bestInitialUnit(v: Duration | undefined): PosibleUnits {
+function bestInitialUnit(v: Duration | undefined): PosibleUnits | undefined {
if (v === undefined) {
- return "seconds";
+ return undefined;
}
if (v.d_ms === "forever") {
- return "seconds";
+ return "forever";
}
if (isDivisible(v.d_ms, 1000 * 60 * 60 * 24)) {
return "days";
@@ -114,10 +122,12 @@ function bestInitialUnit(v: Duration | undefined): PosibleUnits {
}
function translateUnits(
- unit: PosibleUnits,
+ unit: PosibleUnits | undefined,
i18n: InternationalizationAPI,
): TranslatedString {
switch (unit) {
+ case undefined:
+ return i18n.str`Select one...`;
case "days":
return i18n.str`Days`;
case "hours":
@@ -126,6 +136,8 @@ function translateUnits(
return i18n.str`Minutes`;
case "seconds":
return i18n.str`Seconds`;
+ case "forever":
+ return i18n.str`Forever`;
default:
assertUnreachable(unit);
}
@@ -154,9 +166,17 @@ export function InputDurationDropdown<T>({
const initialUnit = bestInitialUnit(value);
- const [unit, setUnit] = useState<PosibleUnits>(initialUnit);
- const [numStr, setNumStr] = useState<string | undefined>(defaultToInputNumberString(value, unit));
+ const [unit, setUnit] = useState<PosibleUnits | undefined>(initialUnit);
+ const [numStr, setNumStr] = useState<string | undefined>(
+ defaultToInputNumberString(value, unit),
+ );
+ useEffect(() => {
+ if (value === undefined && !numStr) return
+ const nu = bestInitialUnit(value)
+ setUnit(nu)
+ setNumStr(defaultToInputNumberString(value, nu))
+ },[value])
// const strValue = durationToString(i18n, value);
return (
@@ -193,14 +213,14 @@ export function InputDurationDropdown<T>({
}}
fromStr={(v) => (!v ? undefined : parseInt(v, 10))}
toStr={(v) => `${v}`}
- disabled={readonly}
+ disabled={readonly || unit === "forever"}
focus={focus}
name={String(name)}
value={numStr}
onChange={(e: h.JSX.TargetedEvent<HTMLInputElement>): void => {
- const d = e.currentTarget.value;
- setNumStr(d);
- const duration = defaultFromInputToDuration(d, unit);
+ const v = e.currentTarget.value;
+ setNumStr(v);
+ const duration = defaultFromInputToDuration(v, unit);
onChange(
(useProtocolDuration && duration
? Duration.toTalerProtocolDuration(duration)
@@ -208,8 +228,8 @@ export function InputDurationDropdown<T>({
);
}}
/>
- </div>
- <div class="field">
+ {/* </div>
+ <div class="field"> */}
<p class={expand ? "control is-expanded select" : "control select "}>
<select
class={error ? "select is-danger" : "select"}
@@ -220,6 +240,9 @@ export function InputDurationDropdown<T>({
const u = e.currentTarget.value as PosibleUnits;
setUnit(u);
const duration = defaultFromInputToDuration(numStr, u);
+ if (!duration || duration.d_ms === "forever") {
+ setNumStr("")
+ }
onChange(
(useProtocolDuration && duration
? Duration.toTalerProtocolDuration(duration)
@@ -229,21 +252,29 @@ export function InputDurationDropdown<T>({
>
{placeholder && <option>{placeholder}</option>}
{UNIT_VALUES.map((v, i) => {
+ if (!withForever && v === "forever") {
+ return undefined;
+ }
return (
<option key={i} value={v} selected={unit === v}>
{translateUnits(v, i18n)}
</option>
);
})}
+ <option key="unslected" value="" selected={unit === undefined}>
+ {translateUnits(undefined, i18n)}
+ </option>
</select>
</p>
- <span class="has-text-grey">{help}</span>
- {error && (
- <p class="help is-danger" style={{ fontSize: 16 }}>
- {error}
- </p>
- )}
+ <div>
+ <span class="has-text-grey">{help}</span>
+ {error && (
+ <p class="help is-danger" style={{ fontSize: 16 }}>
+ {error}
+ </p>
+ )}
+ </div>
</div>
{withForever && (