diff options
Diffstat (limited to 'packages/exchange-backoffice-ui/src')
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"); | 
