aboutsummaryrefslogtreecommitdiff
path: root/packages/aml-backoffice-ui/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'packages/aml-backoffice-ui/src/handlers')
-rw-r--r--packages/aml-backoffice-ui/src/handlers/Calendar.tsx116
-rw-r--r--packages/aml-backoffice-ui/src/handlers/Dialog.tsx15
-rw-r--r--packages/aml-backoffice-ui/src/handlers/FormProvider.tsx25
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputArray.tsx35
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx1
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx1
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputDate.tsx81
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputFile.tsx88
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputLine.tsx36
-rw-r--r--packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx14
-rw-r--r--packages/aml-backoffice-ui/src/handlers/useField.ts5
11 files changed, 303 insertions, 114 deletions
diff --git a/packages/aml-backoffice-ui/src/handlers/Calendar.tsx b/packages/aml-backoffice-ui/src/handlers/Calendar.tsx
new file mode 100644
index 000000000..9da6e1757
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/handlers/Calendar.tsx
@@ -0,0 +1,116 @@
+import { AbsoluteTime } from "@gnu-taler/taler-util"
+import { useTranslationContext } from "@gnu-taler/web-util/browser"
+import { add as dateAdd, sub as dateSub, eachDayOfInterval, endOfMonth, endOfWeek, format, getMonth, getYear, isSameDay, isSameMonth, startOfDay, startOfMonth, startOfWeek } from "date-fns"
+import { VNode, h } from "preact"
+import { useState } from "preact/hooks"
+
+export function Calendar({ value, onChange }: { value: AbsoluteTime | undefined, onChange: (v: AbsoluteTime) => void }): VNode {
+ const today = startOfDay(new Date())
+ const selected = !value ? today : new Date(AbsoluteTime.toStampMs(value))
+ const [showingDate, setShowingDate] = useState(selected)
+ const month = getMonth(showingDate)
+ const year = getYear(showingDate)
+
+ const start = startOfWeek(startOfMonth(showingDate));
+ const end = endOfWeek(endOfMonth(showingDate));
+ const daysInMonth = eachDayOfInterval({ start, end });
+ const { i18n } = useTranslationContext()
+ const monthNames = [
+ i18n.str`January`,
+ i18n.str`February`,
+ i18n.str`March`,
+ i18n.str`April`,
+ i18n.str`May`,
+ i18n.str`June`,
+ i18n.str`July`,
+ i18n.str`August`,
+ i18n.str`September`,
+ i18n.str`October`,
+ i18n.str`November`,
+ i18n.str`December`,
+ ]
+ return <div class="text-center p-2">
+ <div class="flex items-center text-gray-900">
+ <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 round-sm"
+ onClick={() => {
+ setShowingDate(dateSub(showingDate, { years: 1 }))
+ }}>
+ <span class="sr-only">
+ {i18n.str`Previous year`}
+ </span>
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" />
+ </svg>
+ </button>
+ <div class="flex-auto text-sm font-semibold">{year}</div>
+ <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 round-sm"
+ onClick={() => {
+ setShowingDate(dateAdd(showingDate, { years: 1 }))
+ }}>
+ <span class="sr-only">
+ {i18n.str`Next year`}
+ </span>
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
+ </svg>
+ </button>
+ </div>
+ <div class="mt-4 flex items-center text-gray-900">
+ <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 round-sm"
+ onClick={() => {
+ setShowingDate(dateSub(showingDate, { months: 1 }))
+ }}>
+ <span class="sr-only">
+ {i18n.str`Previous month`}
+ </span>
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M12.79 5.23a.75.75 0 01-.02 1.06L8.832 10l3.938 3.71a.75.75 0 11-1.04 1.08l-4.5-4.25a.75.75 0 010-1.08l4.5-4.25a.75.75 0 011.06.02z" clip-rule="evenodd" />
+ </svg>
+ </button>
+ <div class="flex-auto text-sm font-semibold">{monthNames[month]}</div>
+ <button type="button" class="flex px-4 flex-none items-center justify-center p-1.5 text-gray-400 hover:text-gray-500 ring-2 rounded-sm "
+ onClick={() => {
+ setShowingDate(dateAdd(showingDate, { months: 1 }))
+ }}>
+ <span class="sr-only">
+ {i18n.str`Next month`}
+ </span>
+ <svg class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
+ <path fill-rule="evenodd" d="M7.21 14.77a.75.75 0 01.02-1.06L11.168 10 7.23 6.29a.75.75 0 111.04-1.08l4.5 4.25a.75.75 0 010 1.08l-4.5 4.25a.75.75 0 01-1.06-.02z" clip-rule="evenodd" />
+ </svg>
+ </button>
+ </div>
+ <div class="mt-6 grid grid-cols-7 text-xs leading-6 text-gray-500">
+ <div>M</div>
+ <div>T</div>
+ <div>W</div>
+ <div>T</div>
+ <div>F</div>
+ <div>S</div>
+ <div>S</div>
+ </div>
+ <div class="isolate mt-2 grid grid-cols-7 gap-px rounded-lg bg-gray-200 text-sm shadow ring-1 ring-gray-200">
+ {daysInMonth.map(current => (
+ <button type="button"
+ data-month={isSameMonth(current, showingDate)}
+ data-today={isSameDay(current, today)}
+ data-selected={isSameDay(current, selected)}
+ onClick={() => {
+ onChange(AbsoluteTime.fromStampMs(current.getTime()))
+ }}
+ class="text-gray-400 hover:bg-gray-700 focus:z-10 py-1.5
+ data-[month=false]:bg-gray-100 data-[month=true]:bg-white
+ data-[today=true]:font-semibold
+ data-[month=true]:text-gray-900
+ data-[today=true]:bg-red-300 data-[today=true]:hover:bg-red-200
+ data-[month=true]:hover:bg-gray-200
+ data-[selected=true]:!bg-blue-400 data-[selected=true]:hover:!bg-blue-300 ">
+ <time dateTime={format(current, "yyyy-MM-dd")}
+ class="mx-auto flex h-7 w-7 items-center justify-center rounded-full">
+ {format(current, "dd")}
+ </time>
+ </button>
+ ))}
+ </div>
+ </div>
+}
diff --git a/packages/aml-backoffice-ui/src/handlers/Dialog.tsx b/packages/aml-backoffice-ui/src/handlers/Dialog.tsx
new file mode 100644
index 000000000..f9899e94e
--- /dev/null
+++ b/packages/aml-backoffice-ui/src/handlers/Dialog.tsx
@@ -0,0 +1,15 @@
+import { ComponentChildren, VNode, h } from "preact";
+
+export function Dialog({ children, onClose }: { onClose?: () => void; children: ComponentChildren }): VNode {
+ return <div class="relative z-10" aria-labelledby="modal-title" role="dialog" aria-modal="true" onClick={onClose}>
+ <div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"></div>
+
+ <div class="fixed inset-0 z-10 w-screen overflow-y-auto">
+ <div class="flex min-h-full items-center justify-center p-4 text-center sm:items-center sm:p-0">
+ <div class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6">
+ {children}
+ </div>
+ </div>
+ </div>
+ </div>
+}
diff --git a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx b/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx
index 3da2a4f07..310954bd0 100644
--- a/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/FormProvider.tsx
@@ -7,14 +7,13 @@ import { ComponentChildren, VNode, createContext, h } from "preact";
import {
MutableRef,
StateUpdater,
- useEffect,
- useRef,
- useState,
+ useState
} from "preact/hooks";
export interface FormType<T> {
value: MutableRef<Partial<T>>;
initialValue?: Partial<T>;
+ readOnly?: boolean;
onUpdate?: StateUpdater<T>;
computeFormState?: (v: T) => FormState<T>;
}
@@ -24,14 +23,14 @@ export const FormContext = createContext<FormType<any>>({});
export type FormState<T> = {
[field in keyof T]?: T[field] extends AbsoluteTime
- ? Partial<InputFieldState>
- : T[field] extends AmountJson
- ? Partial<InputFieldState>
- : T[field] extends Array<infer P>
- ? Partial<InputArrayFieldState<P>>
- : T[field] extends (object | undefined)
- ? FormState<T[field]>
- : Partial<InputFieldState>;
+ ? Partial<InputFieldState>
+ : T[field] extends AmountJson
+ ? Partial<InputFieldState>
+ : T[field] extends Array<infer P>
+ ? Partial<InputArrayFieldState<P>>
+ : T[field] extends (object | undefined)
+ ? FormState<T[field]>
+ : Partial<InputFieldState>;
};
export interface InputFieldState {
@@ -55,11 +54,13 @@ export function FormProvider<T>({
onUpdate: notify,
onSubmit,
computeFormState,
+ readOnly,
}: {
initialValue?: Partial<T>;
onUpdate?: (v: Partial<T>) => void;
onSubmit?: (v: Partial<T>, s: FormState<T> | undefined) => void;
computeFormState?: (v: Partial<T>) => FormState<T>;
+ readOnly?: boolean;
children: ComponentChildren;
}): VNode {
// const value = useRef(initialValue ?? {});
@@ -79,7 +80,7 @@ export function FormProvider<T>({
};
return (
<FormContext.Provider
- value={{ initialValue, value, onUpdate, computeFormState }}
+ value={{ initialValue, value, onUpdate, computeFormState, readOnly }}
>
<form
onSubmit={(e) => {
diff --git a/packages/aml-backoffice-ui/src/handlers/InputArray.tsx b/packages/aml-backoffice-ui/src/handlers/InputArray.tsx
index 00379bed6..d229b35de 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputArray.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputArray.tsx
@@ -107,22 +107,24 @@ export function InputArray<T extends object, K extends keyof T>(
/>
);
})}
- <div class="pt-2">
- <Option
- label={"Add..." as TranslatedString}
- isSelected={selectedIndex === list.length}
- isLast
- isFirst
- disabled={
- selectedIndex !== undefined && selectedIndex !== list.length
- }
- onClick={() => {
- setSelected(
- selectedIndex === list.length ? undefined : list.length,
- );
- }}
- />
- </div>
+ {!state.disabled &&
+ <div class="pt-2">
+ <Option
+ label={"Add..." as TranslatedString}
+ isSelected={selectedIndex === list.length}
+ isLast
+ isFirst
+ disabled={
+ selectedIndex !== undefined && selectedIndex !== list.length
+ }
+ onClick={() => {
+ setSelected(
+ selectedIndex === list.length ? undefined : list.length,
+ );
+ }}
+ />
+ </div>
+ }
</div>
{selectedIndex !== undefined && (
/**
@@ -131,6 +133,7 @@ export function InputArray<T extends object, K extends keyof T>(
*/
<FormProvider
initialValue={selected}
+ readOnly={state.disabled}
computeFormState={(v) => {
// current state is ignored
// the state is defined by the parent form
diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx
index fdee35447..a5f263615 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputChoiceHorizontal.tsx
@@ -61,6 +61,7 @@ export function InputChoiceHorizontal<T extends object, K extends keyof T>(
return (
<button
type="button"
+ disabled={state.disabled}
class={clazz}
onClick={(e) => {
onChange(
diff --git a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx b/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx
index c37984368..29c596994 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputChoiceStacked.tsx
@@ -60,6 +60,7 @@ export function InputChoiceStacked<T extends object, K extends keyof T>(
type="radio"
name="server-size"
// defaultValue={choice.value}
+ disabled={state.disabled}
value={
(!converter
? (choice.value as string)
diff --git a/packages/aml-backoffice-ui/src/handlers/InputDate.tsx b/packages/aml-backoffice-ui/src/handlers/InputDate.tsx
index 0f286e001..7fcc16b33 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputDate.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputDate.tsx
@@ -1,40 +1,65 @@
import { AbsoluteTime } from "@gnu-taler/taler-util";
import { InputLine, UIFormProps } from "./InputLine.js";
-import { VNode, h } from "preact";
+import { Fragment, VNode, h } from "preact";
import { format, parse } from "date-fns";
+import { Dialog } from "./Dialog.js";
+import { Calendar } from "./Calendar.js";
+import { useState } from "preact/hooks";
+import { useField } from "./useField.js";
export function InputDate<T extends object, K extends keyof T>(
props: { pattern?: string } & UIFormProps<T, K>,
): VNode {
const pattern = props.pattern ?? "dd/MM/yyyy";
+ const [open, setOpen] = useState(false)
+ const { value, onChange } = useField<T, K>(props.name);
return (
- <InputLine<T, K>
- type="text"
- after={{
- type: "icon",
- // icon: <CalendarIcon class="h-6 w-6" />,
- icon: <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
- <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
- </svg>
+ <Fragment>
- }}
- converter={{
- //@ts-ignore
- fromStringUI: (v): AbsoluteTime => {
- if (!v) return AbsoluteTime.never();
- const t_ms = parse(v, pattern, Date.now()).getTime();
- return AbsoluteTime.fromMilliseconds(t_ms);
- },
- //@ts-ignore
- toStringUI: (v: AbsoluteTime) => {
- return !v || !v.t_ms
- ? ""
- : v.t_ms === "never"
- ? "never"
- : format(v.t_ms, pattern);
- },
- }}
- {...props}
- />
+ <InputLine<T, K>
+ type="text"
+ after={{
+ type: "button",
+ onClick: () => {
+ setOpen(true)
+ },
+ // icon: <CalendarIcon class="h-6 w-6" />,
+ children: (
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
+ </svg>)
+ }}
+ converter={{
+ //@ts-ignore
+ fromStringUI: (v): AbsoluteTime | undefined => {
+ if (!v) return undefined;
+ try {
+ const t_ms = parse(v, pattern, Date.now()).getTime();
+ return AbsoluteTime.fromMilliseconds(t_ms);
+ } catch (e) {
+ return undefined;
+ }
+ },
+ //@ts-ignore
+ toStringUI: (v: AbsoluteTime | undefined) => {
+ return !v || !v.t_ms
+ ? undefined
+ : v.t_ms === "never"
+ ? "never"
+ : format(v.t_ms, pattern);
+ },
+ }}
+ {...props}
+ />
+ {open &&
+ <Dialog onClose={() => setOpen(false)}>
+ <Calendar value={value as AbsoluteTime ?? AbsoluteTime.now()}
+ onChange={(v) => {
+ onChange(v as any)
+ setOpen(false)
+ }} />
+ </Dialog>
+ }
+ </Fragment>
);
}
diff --git a/packages/aml-backoffice-ui/src/handlers/InputFile.tsx b/packages/aml-backoffice-ui/src/handlers/InputFile.tsx
index 0d89a98a3..d9af03f86 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputFile.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputFile.tsx
@@ -42,40 +42,42 @@ export function InputFile<T extends object, K extends keyof T>(
clip-rule="evenodd"
/>
</svg>
- <div class="my-2 flex text-sm leading-6 text-gray-600">
- <label
- for="file-upload"
- class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500"
- >
- <span>Upload a file</span>
- <input
- id="file-upload"
- name="file-upload"
- type="file"
- class="sr-only"
- accept={accept}
- onChange={(e) => {
- const f: FileList | null = e.currentTarget.files;
- if (!f || f.length != 1) {
- return onChange(undefined!);
- }
- if (f[0].size > maxBites) {
- return onChange(undefined!);
- }
- return f[0].arrayBuffer().then((b) => {
- const b64 = window.btoa(
- new Uint8Array(b).reduce(
- (data, byte) => data + String.fromCharCode(byte),
- "",
- ),
- );
- return onChange(`data:${f[0].type};base64,${b64}` as any);
- });
- }}
- />
- </label>
- {/* <p class="pl-1">or drag and drop</p> */}
- </div>
+ {!state.disabled &&
+ <div class="my-2 flex text-sm leading-6 text-gray-600">
+ <label
+ for="file-upload"
+ class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500"
+ >
+ <span>Upload a file</span>
+ <input
+ id="file-upload"
+ name="file-upload"
+ type="file"
+ class="sr-only"
+ accept={accept}
+ onChange={(e) => {
+ const f: FileList | null = e.currentTarget.files;
+ if (!f || f.length != 1) {
+ return onChange(undefined!);
+ }
+ if (f[0].size > maxBites) {
+ return onChange(undefined!);
+ }
+ return f[0].arrayBuffer().then((b) => {
+ const b64 = window.btoa(
+ new Uint8Array(b).reduce(
+ (data, byte) => data + String.fromCharCode(byte),
+ "",
+ ),
+ );
+ return onChange(`data:${f[0].type};base64,${b64}` as any);
+ });
+ }}
+ />
+ </label>
+ {/* <p class="pl-1">or drag and drop</p> */}
+ </div>
+ }
</div>
</div>
) : (
@@ -85,14 +87,16 @@ export function InputFile<T extends object, K extends keyof T>(
class=" h-24 w-full object-cover relative"
/>
- <div
- class="opacity-0 hover:opacity-70 duration-300 absolute rounded-lg border inset-0 z-10 flex justify-center text-xl items-center bg-black text-white cursor-pointer "
- onClick={() => {
- onChange(undefined!);
- }}
- >
- Clear
- </div>
+ {!state.disabled &&
+ <div
+ class="opacity-0 hover:opacity-70 duration-300 absolute rounded-lg border inset-0 z-10 flex justify-center text-xl items-center bg-black text-white cursor-pointer "
+ onClick={() => {
+ onChange(undefined!);
+ }}
+ >
+ Clear
+ </div>
+ }
</div>
)}
{help && <p class="text-xs leading-5 text-gray-600 mt-2">{help}</p>}
diff --git a/packages/aml-backoffice-ui/src/handlers/InputLine.tsx b/packages/aml-backoffice-ui/src/handlers/InputLine.tsx
index 9448ef5e4..f6c709d94 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputLine.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputLine.tsx
@@ -1,6 +1,7 @@
import { TranslatedString } from "@gnu-taler/taler-util";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { useField } from "./useField.js";
+import { useEffect, useState } from "preact/hooks";
export interface IconAddon {
type: "icon";
@@ -80,7 +81,7 @@ export function LabelWithTooltipMaybeRequired({
{Label}
<span class="relative flex items-center group pl-2">
{TooltipIcon}
- <div class="absolute bottom-0 flex flex-col items-center hidden mb-6 group-hover:flex">
+ <div class="absolute bottom-0 flex flex-col items-center mb-6 group-hover:flex">
<span class="relative z-10 p-2 text-xs leading-none text-white whitespace-no-wrap bg-black shadow-lg">
{tooltip}
</span>
@@ -110,8 +111,9 @@ function InputWrapper<T extends object, K extends keyof T>({
after,
help,
error,
+ disabled,
required,
-}: { error?: string; children: ComponentChildren } & UIFormProps<T, K>): VNode {
+}: { error?: string; disabled: boolean, children: ComponentChildren } & UIFormProps<T, K>): VNode {
return (
<div class="sm:col-span-6">
<LabelWithTooltipMaybeRequired
@@ -132,6 +134,7 @@ function InputWrapper<T extends object, K extends keyof T>({
) : before.type === "button" ? (
<button
type="button"
+ disabled={disabled}
onClick={before.onClick}
class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-l-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
@@ -153,6 +156,7 @@ function InputWrapper<T extends object, K extends keyof T>({
) : after.type === "button" ? (
<button
type="button"
+ disabled={disabled}
onClick={after.onClick}
class="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50"
>
@@ -189,6 +193,21 @@ export function InputLine<T extends object, K extends keyof T>(
const { name, placeholder, before, after, converter, type } = props;
const { value, onChange, state, isDirty } = useField<T, K>(name);
+ const [text, setText] = useState("")
+ const fromString: (s: string) => any =
+ converter?.fromStringUI ?? defaultFromString;
+ const toString: (s: any) => string = converter?.toStringUI ?? defaultToString;
+
+ useEffect(() => {
+ const newValue = toString(value)
+ if (newValue) {
+
+ setText(newValue)
+ } else {
+ console.log("invalid")
+ }
+ }, [value])
+
if (state.hidden) return <div />;
let clazz =
@@ -233,14 +252,12 @@ export function InputLine<T extends object, K extends keyof T>(
clazz +=
" text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-indigo-600";
}
- const fromString: (s: string) => any =
- converter?.fromStringUI ?? defaultFromString;
- const toString: (s: any) => string = converter?.toStringUI ?? defaultToString;
if (type === "text-area") {
return (
<InputWrapper<T, K>
{...props}
+ disabled={state.disabled}
error={showError ? state.error : undefined}
>
<textarea
@@ -262,15 +279,18 @@ export function InputLine<T extends object, K extends keyof T>(
}
return (
- <InputWrapper<T, K> {...props} error={showError ? state.error : undefined}>
+ <InputWrapper<T, K> {...props} disabled={state.disabled} error={showError ? state.error : undefined}>
<input
name={String(name)}
type={type}
onChange={(e) => {
- onChange(fromString(e.currentTarget.value));
+ setText(e.currentTarget.value)
}}
placeholder={placeholder ? placeholder : undefined}
- value={toString(value) ?? ""}
+ value={text}
+ onBlur={() => {
+ onChange(fromString(text));
+ }}
// defaultValue={toString(value)}
disabled={state.disabled}
aria-invalid={showError}
diff --git a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx b/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx
index 837744827..6e6186a88 100644
--- a/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx
+++ b/packages/aml-backoffice-ui/src/handlers/InputSelectMultiple.tsx
@@ -13,7 +13,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
): VNode {
const { name, label, choices, placeholder, tooltip, required, unique, max } =
props;
- const { value, onChange } = useField<T, K>(name);
+ const { value, onChange, state } = useField<T, K>(name);
const [filter, setFilter] = useState<string | undefined>(undefined);
const regex = new RegExp(`.*${filter}.*`, "i");
@@ -26,8 +26,8 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
filter === undefined
? undefined
: choices.filter((v) => {
- return regex.test(v.label);
- });
+ return regex.test(v.label);
+ });
return (
<div class="sm:col-span-6">
<LabelWithTooltipMaybeRequired
@@ -41,6 +41,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
{choiceMap[v]}
<button
type="button"
+ disabled={state.disabled}
onClick={() => {
const newValue = [...list];
newValue.splice(idx, 1);
@@ -62,7 +63,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
);
})}
- <div class="relative mt-2">
+ {!state.disabled && <div class="relative mt-2">
<input
id="combobox"
type="text"
@@ -78,6 +79,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
/>
<button
type="button"
+ disabled={state.disabled}
onClick={() => {
setFilter(filter === undefined ? "" : undefined);
}}
@@ -122,7 +124,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
onChange(newValue as T[K]);
}}
- // tabindex="-1"
+ // tabindex="-1"
>
{/* <!-- Selected: "font-semibold" --> */}
<span class="block truncate">{v.label}</span>
@@ -145,7 +147,7 @@ export function InputSelectMultiple<T extends object, K extends keyof T>(
{/* <!-- More items... --> */}
</ul>
)}
- </div>
+ </div>}
</div>
);
}
diff --git a/packages/aml-backoffice-ui/src/handlers/useField.ts b/packages/aml-backoffice-ui/src/handlers/useField.ts
index bf94d2f5d..7eec5c5f8 100644
--- a/packages/aml-backoffice-ui/src/handlers/useField.ts
+++ b/packages/aml-backoffice-ui/src/handlers/useField.ts
@@ -16,6 +16,7 @@ export function useField<T extends object, K extends keyof T>(
value: formValue,
computeFormState,
onUpdate: notifyUpdate,
+ readOnly: readOnlyForm,
} = useContext(FormContext);
type P = typeof name;
@@ -30,8 +31,8 @@ export function useField<T extends object, K extends keyof T>(
//compute default state
const state = {
- disabled: fieldState.disabled ?? false,
- readonly: fieldState.readonly ?? false,
+ disabled: readOnlyForm ? true : (fieldState.disabled ?? false),
+ readonly: readOnlyForm ? true : (fieldState.readonly ?? false),
hidden: fieldState.hidden ?? false,
error: fieldState.error,
elements: "elements" in fieldState ? fieldState.elements ?? [] : [],