aboutsummaryrefslogtreecommitdiff
path: root/packages/exchange-backoffice-ui
diff options
context:
space:
mode:
Diffstat (limited to 'packages/exchange-backoffice-ui')
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputArray.tsx22
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx14
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputDate.tsx12
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputFile.tsx19
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx10
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputLine.tsx25
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx12
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx14
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputText.tsx4
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx4
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/forms.ts32
-rw-r--r--packages/exchange-backoffice-ui/src/handlers/useField.ts29
12 files changed, 123 insertions, 74 deletions
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx b/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx
index c09806e58..00379bed6 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputArray.tsx
@@ -71,17 +71,15 @@ function Option({
);
}
-export function InputArray(
+export function InputArray<T extends object, K extends keyof T>(
props: {
fields: UIFormField[];
labelField: string;
- } & UIFormProps<Array<{}>>,
+ } & UIFormProps<T, K>,
): VNode {
const { fields, labelField, name, label, required, tooltip } = props;
- const { value, onChange, state } = useField<{ [s: string]: Array<any> }>(
- name,
- );
- const list = value ?? [];
+ const { value, onChange, state } = useField<T, K>(name);
+ const list = (value ?? []) as Array<Record<string, string | undefined>>;
const [selectedIndex, setSelected] = useState<number | undefined>(undefined);
const selected =
selectedIndex === undefined ? undefined : list[selectedIndex];
@@ -98,7 +96,7 @@ export function InputArray(
{list.map((v, idx) => {
return (
<Option
- label={v[labelField]}
+ label={v[labelField] as TranslatedString}
isSelected={selectedIndex === idx}
isLast={idx === list.length - 1}
disabled={selectedIndex !== undefined && selectedIndex !== idx}
@@ -141,10 +139,16 @@ export function InputArray(
//@ts-ignore
return state.elements[selectedIndex];
}}
+ onSubmit={(v) => {
+ const newValue = [...list];
+ newValue.splice(selectedIndex, 1, v);
+ onChange(newValue as T[K]);
+ setSelected(undefined);
+ }}
onUpdate={(v) => {
const newValue = [...list];
newValue.splice(selectedIndex, 1, v);
- onChange(newValue);
+ onChange(newValue as T[K]);
}}
>
<div class="px-4 py-6">
@@ -163,7 +167,7 @@ export function InputArray(
onClick={() => {
const newValue = [...list];
newValue.splice(selectedIndex, 1);
- onChange(newValue);
+ onChange(newValue as T[K]);
setSelected(undefined);
}}
class="block rounded-md bg-red-600 px-3 py-2 text-center text-sm text-white shadow-sm hover:bg-red-500 "
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx b/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx
index 361eb39a3..3bce0123f 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputChoiceStacked.tsx
@@ -9,10 +9,10 @@ export interface Choice {
value: string;
}
-export function InputChoiceStacked(
+export function InputChoiceStacked<T extends object, K extends keyof T>(
props: {
choices: Choice[];
- } & UIFormProps<string>,
+ } & UIFormProps<T, K>,
): VNode {
const {
choices,
@@ -26,9 +26,7 @@ export function InputChoiceStacked(
after,
converter,
} = props;
- const { value, onChange, state, isDirty } = useField<{
- [s: string]: undefined | string;
- }>(name);
+ const { value, onChange, state, isDirty } = useField<T, K>(name);
if (state.hidden) {
return <Fragment />;
}
@@ -58,7 +56,11 @@ export function InputChoiceStacked(
name="server-size"
defaultValue={choice.value}
onClick={(e) => {
- onChange(value === choice.value ? undefined : choice.value);
+ onChange(
+ (value === choice.value
+ ? undefined
+ : choice.value) as T[K],
+ );
}}
class="sr-only"
aria-labelledby="server-size-0-label"
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx b/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx
index 00dd59996..e834b6cdb 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputDate.tsx
@@ -4,24 +4,26 @@ import { CalendarIcon } from "@heroicons/react/24/outline";
import { VNode, h } from "preact";
import { format, parse } from "date-fns";
-export function InputDate(
- props: { pattern?: string } & UIFormProps<AbsoluteTime>,
+export function InputDate<T extends object, K extends keyof T>(
+ props: { pattern?: string } & UIFormProps<T, K>,
): VNode {
const pattern = props.pattern ?? "dd/MM/yyyy";
return (
- <InputLine<AbsoluteTime>
+ <InputLine<T, K>
type="text"
after={{
type: "icon",
icon: <CalendarIcon class="h-6 w-6" />,
}}
converter={{
- fromStringUI: (v) => {
+ //@ts-ignore
+ fromStringUI: (v): AbsoluteTime => {
if (!v) return { t_ms: "never" };
const t_ms = parse(v, pattern, Date.now()).getTime();
return { t_ms };
},
- toStringUI: (v) => {
+ //@ts-ignore
+ toStringUI: (v: AbsoluteTime) => {
return !v || !v.t_ms
? ""
: v.t_ms === "never"
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx b/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx
index 37216c982..0d89a98a3 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputFile.tsx
@@ -1,13 +1,9 @@
import { Fragment, VNode, h } from "preact";
-import {
- InputLine,
- LabelWithTooltipMaybeRequired,
- UIFormProps,
-} from "./InputLine.js";
+import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
import { useField } from "./useField.js";
-export function InputFile(
- props: { maxBites: number; accept?: string } & UIFormProps<string>,
+export function InputFile<T extends object, K extends keyof T>(
+ props: { maxBites: number; accept?: string } & UIFormProps<T, K>,
): VNode {
const {
name,
@@ -19,7 +15,7 @@ export function InputFile(
maxBites,
accept,
} = props;
- const { value, onChange, state } = useField<{ [s: string]: string }>(name);
+ const { value, onChange, state } = useField<T, K>(name);
if (state.hidden) {
return <div />;
@@ -31,7 +27,7 @@ export function InputFile(
tooltip={tooltip}
required={required}
/>
- {!value || !value.startsWith("data:image/") ? (
+ {!value || !(value as string).startsWith("data:image/") ? (
<div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 py-1">
<div class="text-center">
<svg
@@ -84,7 +80,10 @@ export function InputFile(
</div>
) : (
<div class="mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 relative">
- <img src={value} class=" h-24 w-full object-cover relative" />
+ <img
+ src={value as string}
+ 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 "
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx b/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx
index 49e6973fc..fb04e3852 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputInteger.tsx
@@ -1,15 +1,19 @@
import { VNode, h } from "preact";
import { InputLine, UIFormProps } from "./InputLine.js";
-export function InputInteger(props: UIFormProps<number>): VNode {
+export function InputInteger<T extends object, K extends keyof T>(
+ props: UIFormProps<T, K>,
+): VNode {
return (
<InputLine
type="number"
converter={{
- fromStringUI: (v) => {
+ //@ts-ignore
+ fromStringUI: (v): number => {
return !v ? 0 : Number.parseInt(v, 10);
},
- toStringUI: (v?: number) => {
+ //@ts-ignore
+ toStringUI: (v?: number): string => {
return v === undefined ? "" : String(v);
},
}}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx b/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx
index 32b16313d..8e847a273 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputLine.tsx
@@ -22,8 +22,8 @@ interface StringConverter<T> {
fromStringUI: (v?: string) => T;
}
-export interface UIFormProps<T> {
- name: keyof T;
+export interface UIFormProps<T extends object, K extends keyof T> {
+ name: K;
label: TranslatedString;
placeholder?: TranslatedString;
tooltip?: TranslatedString;
@@ -31,7 +31,7 @@ export interface UIFormProps<T> {
before?: Addon;
after?: Addon;
required?: boolean;
- converter?: StringConverter<T>;
+ converter?: StringConverter<T[K]>;
}
export type FormErrors<T> = {
@@ -102,7 +102,7 @@ export function LabelWithTooltipMaybeRequired({
return WithTooltip;
}
-function InputWrapper<T>({
+function InputWrapper<T extends object, K extends keyof T>({
children,
label,
tooltip,
@@ -111,7 +111,7 @@ function InputWrapper<T>({
help,
error,
required,
-}: { error?: string; children: ComponentChildren } & UIFormProps<T>): VNode {
+}: { error?: string; children: ComponentChildren } & UIFormProps<T, K>): VNode {
return (
<div class="sm:col-span-6">
<LabelWithTooltipMaybeRequired
@@ -181,13 +181,13 @@ function defaultFromString(v: string) {
return v;
}
-type InputType = "text" | "text-area" | "password" | "email";
+type InputType = "text" | "text-area" | "password" | "email" | "number";
-export function InputLine<T>(
- props: { type: InputType } & UIFormProps<T>,
+export function InputLine<T extends object, K extends keyof T>(
+ props: { type: InputType } & UIFormProps<T, K>,
): VNode {
const { name, placeholder, before, after, converter, type } = props;
- const { value, onChange, state, isDirty } = useField(name);
+ const { value, onChange, state, isDirty } = useField<T, K>(name);
if (state.hidden) return <div />;
@@ -239,7 +239,10 @@ export function InputLine<T>(
if (type === "text-area") {
return (
- <InputWrapper<T> {...props} error={showError ? state.error : undefined}>
+ <InputWrapper<T, K>
+ {...props}
+ error={showError ? state.error : undefined}
+ >
<textarea
rows={4}
name={String(name)}
@@ -258,7 +261,7 @@ export function InputLine<T>(
}
return (
- <InputWrapper<T> {...props} error={showError ? state.error : undefined}>
+ <InputWrapper<T, K> {...props} error={showError ? state.error : undefined}>
<input
name={String(name)}
type={type}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx b/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx
index dd85453e5..8369e509d 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputSelectMultiple.tsx
@@ -4,16 +4,16 @@ import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
import { useField } from "./useField.js";
import { useState } from "preact/hooks";
-export function InputSelectMultiple(
+export function InputSelectMultiple<T extends object, K extends keyof T>(
props: {
choices: Choice[];
unique?: boolean;
max?: number;
- } & UIFormProps<Array<string>>,
+ } & UIFormProps<T, K>,
): VNode {
const { name, label, choices, placeholder, tooltip, required, unique, max } =
props;
- const { value, onChange } = useField<{ [s: string]: Array<string> }>(name);
+ const { value, onChange } = useField<T, K>(name);
const [filter, setFilter] = useState<string | undefined>(undefined);
const regex = new RegExp(`.*${filter}.*`, "i");
@@ -21,7 +21,7 @@ export function InputSelectMultiple(
return { ...prev, [curr.value]: curr.label };
}, {} as Record<string, string>);
- const list = value ?? [];
+ const list = (value ?? []) as string[];
const filteredChoices =
filter === undefined
? undefined
@@ -44,7 +44,7 @@ export function InputSelectMultiple(
onClick={() => {
const newValue = [...list];
newValue.splice(idx, 1);
- onChange(newValue);
+ onChange(newValue as T[K]);
setFilter(undefined);
}}
class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20"
@@ -119,7 +119,7 @@ export function InputSelectMultiple(
}
const newValue = [...list];
newValue.splice(0, 0, v.value);
- onChange(newValue);
+ onChange(newValue as T[K]);
}}
// tabindex="-1"
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx b/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx
index a4ed1ba1c..9af446e6c 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputSelectOne.tsx
@@ -4,15 +4,13 @@ import { LabelWithTooltipMaybeRequired, UIFormProps } from "./InputLine.js";
import { useField } from "./useField.js";
import { useState } from "preact/hooks";
-export function InputSelectOne(
+export function InputSelectOne<T extends object, K extends keyof T>(
props: {
choices: Choice[];
- } & UIFormProps<Array<string>>,
+ } & UIFormProps<T, K>,
): VNode {
const { name, label, choices, placeholder, tooltip, required } = props;
- const { value, onChange } = useField<{ [s: string]: string | undefined }>(
- name,
- );
+ const { value, onChange } = useField<T, K>(name);
const [filter, setFilter] = useState<string | undefined>(undefined);
const regex = new RegExp(`.*${filter}.*`, "i");
@@ -35,11 +33,11 @@ export function InputSelectOne(
/>
{value ? (
<span class="inline-flex items-center gap-x-0.5 rounded-md bg-gray-100 p-1 mr-2 font-medium text-gray-600">
- {choiceMap[value]}
+ {choiceMap[value as string]}
<button
type="button"
onClick={() => {
- onChange(undefined);
+ onChange(undefined!);
}}
class="group relative h-5 w-5 rounded-sm hover:bg-gray-500/20"
>
@@ -103,7 +101,7 @@ export function InputSelectOne(
role="option"
onClick={() => {
setFilter(undefined);
- onChange(v.value);
+ onChange(v.value as T[K]);
}}
// tabindex="-1"
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputText.tsx b/packages/exchange-backoffice-ui/src/handlers/InputText.tsx
index 014730d92..1b37ee6fb 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputText.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputText.tsx
@@ -1,6 +1,8 @@
import { VNode, h } from "preact";
import { InputLine, UIFormProps } from "./InputLine.js";
-export function InputText<T>(props: UIFormProps<T>): VNode {
+export function InputText<T extends object, K extends keyof T>(
+ props: UIFormProps<T, K>,
+): VNode {
return <InputLine type="text" {...props} />;
}
diff --git a/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx b/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx
index b819a5549..45229951e 100644
--- a/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx
+++ b/packages/exchange-backoffice-ui/src/handlers/InputTextArea.tsx
@@ -1,6 +1,8 @@
import { VNode, h } from "preact";
import { InputLine, UIFormProps } from "./InputLine.js";
-export function InputTextArea(props: UIFormProps<string>): VNode {
+export function InputTextArea<T extends object, K extends keyof T>(
+ props: UIFormProps<T, K>,
+): VNode {
return <InputLine type="text-area" {...props} />;
}
diff --git a/packages/exchange-backoffice-ui/src/handlers/forms.ts b/packages/exchange-backoffice-ui/src/handlers/forms.ts
index a97b8561d..115127cc3 100644
--- a/packages/exchange-backoffice-ui/src/handlers/forms.ts
+++ b/packages/exchange-backoffice-ui/src/handlers/forms.ts
@@ -25,18 +25,18 @@ type DoubleColumnFormSection = {
/**
* Constrain the type with the ui props
*/
-type FieldType = {
+type FieldType<T extends object = any, K extends keyof T = any> = {
group: Parameters<typeof Group>[0];
caption: Parameters<typeof Caption>[0];
- array: Parameters<typeof InputArray>[0];
- file: Parameters<typeof InputFile>[0];
- selectOne: Parameters<typeof InputSelectOne>[0];
- selectMultiple: Parameters<typeof InputSelectMultiple>[0];
- text: Parameters<typeof InputText>[0];
- textArea: Parameters<typeof InputTextArea>[0];
- choiceStacked: Parameters<typeof InputChoiceStacked>[0];
- date: Parameters<typeof InputDate>[0];
- integer: Parameters<typeof InputInteger>[0];
+ array: Parameters<typeof InputArray<T, K>>[0];
+ file: Parameters<typeof InputFile<T, K>>[0];
+ selectOne: Parameters<typeof InputSelectOne<T, K>>[0];
+ selectMultiple: Parameters<typeof InputSelectMultiple<T, K>>[0];
+ text: Parameters<typeof InputText<T, K>>[0];
+ textArea: Parameters<typeof InputTextArea<T, K>>[0];
+ choiceStacked: Parameters<typeof InputChoiceStacked<T, K>>[0];
+ date: Parameters<typeof InputDate<T, K>>[0];
+ integer: Parameters<typeof InputInteger<T, K>>[0];
};
/**
@@ -69,14 +69,20 @@ type UIFormFieldMap = {
const UIFormConfiguration: UIFormFieldMap = {
group: Group,
caption: Caption,
+ //@ts-ignore
array: InputArray,
text: InputText,
+ //@ts-ignore
file: InputFile,
textArea: InputTextArea,
+ //@ts-ignore
date: InputDate,
+ //@ts-ignore
choiceStacked: InputChoiceStacked,
integer: InputInteger,
+ //@ts-ignore
selectOne: InputSelectOne,
+ //@ts-ignore
selectMultiple: InputSelectMultiple,
};
@@ -97,11 +103,11 @@ export function RenderAllFieldsByUiConfig({
);
}
-type FormSet<T> = {
+type FormSet<T extends object, K extends keyof T = any> = {
Provider: typeof FormProvider<T>;
- InputLine: typeof InputLine<T>;
+ InputLine: typeof InputLine<T, K>;
};
-export function createNewForm<T>(): FormSet<T> {
+export function createNewForm<T extends object>(): FormSet<T> {
return {
Provider: FormProvider,
InputLine: InputLine,
diff --git a/packages/exchange-backoffice-ui/src/handlers/useField.ts b/packages/exchange-backoffice-ui/src/handlers/useField.ts
index 3be397314..94635646f 100644
--- a/packages/exchange-backoffice-ui/src/handlers/useField.ts
+++ b/packages/exchange-backoffice-ui/src/handlers/useField.ts
@@ -12,7 +12,9 @@ export interface InputFieldHandler<Type> {
isDirty: boolean;
}
-export function useField<T>(name: keyof T): InputFieldHandler<T[keyof T]> {
+export function useField<T extends object, K extends keyof T>(
+ name: K,
+): InputFieldHandler<T[K]> {
const {
initialValue,
value: formValue,
@@ -78,3 +80,28 @@ function setValueDeeper(object: any, names: string[], value: any): any {
}
return { ...object, [head]: setValueDeeper(object[head] ?? {}, rest, value) };
}
+
+type TTT<T extends object, K extends keyof T, R> = K extends keyof T
+ ? R extends T[K]
+ ? number
+ : never
+ : never;
+
+function impl<T extends object, K extends keyof T, R extends T[K]>(
+ obj: T,
+ name: K,
+): T[K] {
+ return obj[name];
+}
+
+interface Pepe {
+ name: string;
+ when: Date;
+ size: number;
+}
+const p: Pepe = {
+ name: "n",
+ when: new Date(),
+ size: 1,
+};
+const a = impl(p, "size");