foxygit / RPA-Remisser Log in
commits tags

/webui/frontend/src/components/InputsEditor.tsx · 3.77 KB

raw
import { useEffect, useRef, useState } from "react";
import type { Flow, FlowInput } from "../lib/api";
import type { Schema } from "../lib/schema";
import { fieldLabel } from "../lib/schema";
import type { Lang, T } from "../lib/i18n";

interface Props {
  open: boolean;
  t: T;
  lang: Lang;
  schema: Schema;
  flow: Flow;
  onChange: (patch: { inputs: FlowInput[]; input_sets: Record<string, Record<string, string>> }) => void;
  onClose: () => void;
}

export function InputsEditor({ open, t, lang, schema, flow, onChange, onClose }: Props) {
  const ref = useRef<HTMLDialogElement>(null);
  const [inputs, setInputs] = useState<FlowInput[]>([]);
  const [sets, setSets] = useState<Record<string, Record<string, string>>>({});

  useEffect(() => {
    const d = ref.current;
    if (!d) return;
    if (open && !d.open) {
      d.showModal();
      setInputs((flow.inputs ?? []).map((i) => ({ ...i })));
      setSets({ ...(flow.input_sets ?? {}) });
    }
    if (!open && d.open) d.close();
  }, [open, flow.inputs, flow.input_sets]);

  const fieldDefs = schema.input_fields ?? [];

  function setInput(i: number, patch: Partial<FlowInput>) {
    setInputs((prev) => prev.map((x, j) => (j === i ? { ...x, ...patch } : x)));
  }

  function save() {
    const cleaned = inputs.filter((i) => (i.name ?? "").trim());
    onChange({ inputs: cleaned, input_sets: sets });
    onClose();
  }

  return (
    <dialog ref={ref} onClose={onClose}>
      <h2>{t("inputs_title")}</h2>
      <p className="hint">{t("inputs_hint")}</p>

      <div className="inputs-list">
        {inputs.map((inp, i) => (
          <div className="input-row" key={i}>
            <div className="fields">
              {fieldDefs.map((f) => (
                <label className="field" key={f.key} title={f.help}>
                  <span>{fieldLabel(f, lang)}</span>
                  <input
                    type="text"
                    value={String(inp[f.key as keyof FlowInput] ?? "")}
                    onChange={(e) => setInput(i, { [f.key]: e.target.value } as Partial<FlowInput>)}
                  />
                </label>
              ))}
            </div>
            <button className="danger tiny" onClick={() => setInputs((prev) => prev.filter((_, j) => j !== i))}>
              {t("remove")}
            </button>
          </div>
        ))}
        <button className="secondary" onClick={() => setInputs((prev) => [...prev, { name: "" }])}>
          {t("inputs_add")}
        </button>
      </div>

      {Object.keys(sets).length > 0 && (
        <>
          <strong className="inputs-sets-title">{t("inputs_sets")}</strong>
          {Object.entries(sets).map(([name, vals]) => (
            <div className="set-row" key={name}>
              <span>
                {name} <span className="hint">({Object.keys(vals).length})</span>
              </span>
              <span className="spacer" />
              <button
                className="secondary tiny"
                onClick={() => {
                  const nn = prompt(t("inputs_set_rename"), name);
                  if (nn && nn !== name) {
                    setSets(({ [name]: v, ...rest }) => ({ ...rest, [nn]: v }));
                  }
                }}
              >
                {t("inputs_set_rename")}
              </button>
              <button
                className="danger tiny"
                onClick={() => setSets(({ [name]: _drop, ...rest }) => rest)}
              >
                {t("remove")}
              </button>
            </div>
          ))}
        </>
      )}

      <div className="dialog-actions">
        <button type="button" className="secondary" onClick={onClose}>
          {t("cancel")}
        </button>
        <button type="button" onClick={save}>
          {t("save")}
        </button>
      </div>
    </dialog>
  );
}