Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,13 @@
|
||||
import { FormulaComponentDto } from "../dtos/FormulaDto";
|
||||
import FormulaHelpers from "../utils/FormulaHelpers";
|
||||
|
||||
export default function FormulaComponentBadge({ item }: { item: FormulaComponentDto }) {
|
||||
return (
|
||||
<div>
|
||||
{item.type === "variable" && <span className="text-muted-foreground text-sm">{item.value}</span>}
|
||||
{item.type === "operator" && <span className="text-foreground text-sm font-bold">{FormulaHelpers.getOperatorSymbol(item.value)}</span>}
|
||||
{item.type === "parenthesis" && <span className="text-muted-foreground">{item.value === "OPEN" ? "(" : ")"}</span>}
|
||||
{item.type === "value" && <span className="text-sm font-bold text-blue-900">{item.value}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import { Form } from "react-router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ButtonPrimary from "~/components/ui/buttons/ButtonPrimary";
|
||||
import ButtonSecondary from "~/components/ui/buttons/ButtonSecondary";
|
||||
import InputText from "~/components/ui/input/InputText";
|
||||
import Modal from "~/components/ui/modals/Modal";
|
||||
import { FormulaComponentDto, FormulaOperatorTypes, FormulaComponentType } from "../dtos/FormulaDto";
|
||||
import FormulaHelpers from "../utils/FormulaHelpers";
|
||||
import InputSelect from "~/components/ui/input/InputSelect";
|
||||
import InputRadioGroupCards from "~/components/ui/input/InputRadioGroupCards";
|
||||
|
||||
export default function FormulaComponentModal({
|
||||
item,
|
||||
idx,
|
||||
order,
|
||||
open,
|
||||
onClose,
|
||||
onSave,
|
||||
onRemove,
|
||||
}: {
|
||||
item: FormulaComponentDto | undefined;
|
||||
idx: number | undefined;
|
||||
order: number;
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (item: FormulaComponentDto) => void;
|
||||
onRemove?: (idx: number) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [type, setType] = useState<FormulaComponentType>(item?.type ?? "variable");
|
||||
const [value, setValue] = useState<string>(item?.value ?? "");
|
||||
|
||||
useEffect(() => {
|
||||
setType(item?.type ?? "variable");
|
||||
setValue(item?.value ?? "");
|
||||
}, [item]);
|
||||
|
||||
function onConfirm() {
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
onSave({ order, type, value });
|
||||
}
|
||||
|
||||
function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault();
|
||||
onConfirm();
|
||||
}
|
||||
function onClosing() {
|
||||
setType("variable");
|
||||
setValue("");
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={onClosing} size="lg">
|
||||
<Form onSubmit={onSubmit} className="inline-block h-full w-full overflow-visible p-1 text-left align-bottom sm:align-middle">
|
||||
<input name="action" type="hidden" value="create" readOnly hidden />
|
||||
<div className="mt-3">
|
||||
<h3 className="text-foreground text-lg font-medium leading-6">
|
||||
{idx === undefined ? t("pages.formulas.components.new") : t("pages.formulas.components.edit")}
|
||||
</h3>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2" key={idx}>
|
||||
<div className="grid gap-2">
|
||||
<div className="">
|
||||
<InputRadioGroupCards
|
||||
columns={3}
|
||||
name="type"
|
||||
value={type}
|
||||
onChange={(e) => setType(e?.toString() as FormulaComponentType)}
|
||||
required
|
||||
options={[
|
||||
{ name: t("pages.formulas.components.types.variable"), value: "variable" },
|
||||
{ name: t("pages.formulas.components.types.operator"), value: "operator" },
|
||||
// { name: "parenthesis", value: "parenthesis" },
|
||||
{ name: t("pages.formulas.components.types.value"), value: "value" },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{type === "variable" ? (
|
||||
<InputText autoFocus name="name" title={t("shared.name")} value={value} setValue={(e) => setValue(e.toString())} required />
|
||||
) : type === "operator" ? (
|
||||
<InputSelect
|
||||
autoFocus
|
||||
name="operator"
|
||||
title={t("pages.formulas.operator")}
|
||||
value={value}
|
||||
setValue={(e) => setValue(FormulaHelpers.getOperatorType(e?.toString() ?? ""))}
|
||||
required
|
||||
options={FormulaOperatorTypes.map((item) => {
|
||||
return { name: t(`pages.formulas.operators.${item}`), value: item };
|
||||
})}
|
||||
/>
|
||||
) : type === "parenthesis" ? (
|
||||
<InputSelect
|
||||
autoFocus
|
||||
name="parenthesis"
|
||||
title={t("pages.formulas.parenthesis.title")}
|
||||
value={value}
|
||||
setValue={(e) => setValue(FormulaHelpers.getParenthesisType(e?.toString() ?? ""))}
|
||||
required
|
||||
options={["OPEN", "CLOSE"].map((item) => {
|
||||
return { name: t(`pages.formulas.parenthesis.${item}`), value: item };
|
||||
})}
|
||||
/>
|
||||
) : type === "value" ? (
|
||||
<InputText autoFocus name="value" title={t("shared.value")} value={value} setValue={(e) => setValue(e.toString())} required />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex justify-between space-x-2">
|
||||
<div>
|
||||
{onRemove && idx !== undefined && (
|
||||
<ButtonSecondary
|
||||
type="button"
|
||||
destructive
|
||||
onClick={() => {
|
||||
onRemove(idx);
|
||||
onClosing();
|
||||
}}
|
||||
>
|
||||
{t("shared.remove")}
|
||||
</ButtonSecondary>
|
||||
)}
|
||||
</div>
|
||||
<ButtonPrimary type="submit">{t("shared.save")}</ButtonPrimary>
|
||||
</div>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { useNavigation, useNavigate, Form } from "react-router";
|
||||
import { useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ButtonSecondary from "~/components/ui/buttons/ButtonSecondary";
|
||||
import LoadingButton from "~/components/ui/buttons/LoadingButton";
|
||||
import InputText from "~/components/ui/input/InputText";
|
||||
import InputSelect from "~/components/ui/input/InputSelect";
|
||||
import { FormulaCalculationTriggerTypes, FormulaComponentDto, FormulaDto, FormulaResultAsTypes } from "../dtos/FormulaDto";
|
||||
import FormulaHelpers from "../utils/FormulaHelpers";
|
||||
import FormulaComponentModal from "./FormulaComponentModal";
|
||||
import ConfirmModal, { RefConfirmModal } from "~/components/ui/modals/ConfirmModal";
|
||||
import { useAppOrAdminData } from "~/utils/data/useAppOrAdminData";
|
||||
import { getUserHasPermission } from "~/utils/helpers/PermissionsHelper";
|
||||
import InputCheckboxWithDescription from "~/components/ui/input/InputCheckboxWithDescription";
|
||||
import FormulaComponentBadge from "./FormulaComponentBadge";
|
||||
import { adminPath } from "~/utils/app/CanonicalUrlUtils";
|
||||
|
||||
export default function FormulaForm({ item, onDelete }: { item?: FormulaDto; onDelete?: () => void }) {
|
||||
const { t } = useTranslation();
|
||||
const appOrAdminData = useAppOrAdminData();
|
||||
const navigation = useNavigation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const confirmModalDelete = useRef<RefConfirmModal>(null);
|
||||
|
||||
const [showModal, setShowModal] = useState<{
|
||||
item?: FormulaComponentDto;
|
||||
idx?: number;
|
||||
}>();
|
||||
|
||||
const [name, setName] = useState(item?.name || "");
|
||||
const [description, setDescription] = useState(item?.description || "");
|
||||
const [resultAs, setResultAs] = useState(item?.resultAs || "number");
|
||||
const [calculationTrigger, setCalculationTrigger] = useState(item?.calculationTrigger || "IF_UNSET");
|
||||
const [withLogs, setWithLogs] = useState(item?.withLogs || false);
|
||||
|
||||
const [components, setComponents] = useState<FormulaComponentDto[]>(item?.components || []);
|
||||
|
||||
function onSave(item: FormulaComponentDto) {
|
||||
const idx = showModal?.idx;
|
||||
if (idx !== undefined) {
|
||||
components[idx] = item;
|
||||
} else {
|
||||
components.push({
|
||||
...item,
|
||||
order: components.length + 1,
|
||||
});
|
||||
}
|
||||
setComponents([...components]);
|
||||
setShowModal(undefined);
|
||||
}
|
||||
function getActionName() {
|
||||
return item ? "edit" : "new";
|
||||
}
|
||||
function onDeleteClick() {
|
||||
confirmModalDelete.current?.show(
|
||||
t("pages.formulas.confirmDelete.title"),
|
||||
t("shared.delete"),
|
||||
t("shared.cancel"),
|
||||
t("pages.formulas.confirmDelete.description")
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Form method="post" className="inline-block w-full overflow-hidden p-1 text-left align-bottom sm:align-middle">
|
||||
<input type="hidden" name="action" value={getActionName()} hidden readOnly />
|
||||
{components.map((component, index) => {
|
||||
return <input type="hidden" name="components[]" value={JSON.stringify(component)} key={index} hidden readOnly />;
|
||||
})}
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<InputText autoFocus name="name" title={t("shared.name")} value={name} setValue={setName} required />
|
||||
<InputText name="description" title={t("pages.formulas.fields.description")} value={description} setValue={setDescription} />
|
||||
<InputSelect
|
||||
name="resultAs"
|
||||
title={t("pages.formulas.fields.resultAs")}
|
||||
value={resultAs}
|
||||
setValue={(e) => setResultAs(FormulaHelpers.getResultAs(e?.toString() ?? ""))}
|
||||
options={FormulaResultAsTypes.map((item) => {
|
||||
return { name: t(`pages.formulas.resultAs.${item}`), value: item };
|
||||
})}
|
||||
/>
|
||||
|
||||
<InputSelect
|
||||
name="calculationTrigger"
|
||||
title={t("pages.formulas.fields.calculationTrigger")}
|
||||
value={calculationTrigger}
|
||||
setValue={(e) => setCalculationTrigger(FormulaHelpers.getCalculationTrigger(e?.toString() ?? ""))}
|
||||
options={FormulaCalculationTriggerTypes.map((item) => {
|
||||
return { name: t(`pages.formulas.calculationTriggers.${item}`), value: item };
|
||||
})}
|
||||
/>
|
||||
|
||||
<InputCheckboxWithDescription
|
||||
className="col-span-2"
|
||||
name="withLogs"
|
||||
title={t("pages.formulas.fields.withLogs")}
|
||||
description={t("pages.formulas.fields.withLogsDescription")}
|
||||
value={withLogs}
|
||||
setValue={setWithLogs}
|
||||
/>
|
||||
|
||||
<div className="col-span-2">
|
||||
<div className="mb-1 flex items-center justify-between space-x-2 text-xs">
|
||||
<label className="text-muted-foreground font-medium">{t("pages.formulas.components.title")}</label>
|
||||
<button type="button" onClick={() => setComponents([])} className="text-muted-foreground hover:text-foreground/80">
|
||||
{t("shared.clear")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="text-foreground flex flex-wrap">
|
||||
{components.map((item, idx) => {
|
||||
return (
|
||||
<button
|
||||
key={idx}
|
||||
type="button"
|
||||
onClick={() => setShowModal({ item, idx: idx })}
|
||||
className="border-border hover:border-border relative m-1 block rounded-lg border-2 border-dashed p-1 text-center text-sm focus:outline-hidden focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
|
||||
>
|
||||
<FormulaComponentBadge item={item} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
<div className="">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowModal({ item: undefined, idx: undefined })}
|
||||
className="relative m-1 block w-full rounded-lg border-2 border-dashed border-blue-300 bg-blue-50 p-1 text-center text-blue-600 hover:border-blue-400 hover:bg-blue-100 focus:outline-hidden focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
>
|
||||
<span className="block text-sm font-medium">{t("pages.formulas.components.add")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex justify-between space-x-2 sm:mt-6">
|
||||
<div>
|
||||
{onDelete && getUserHasPermission(appOrAdminData, "admin.formulas.delete") && (
|
||||
<ButtonSecondary disabled={navigation.state === "submitting"} type="button" className="w-full" onClick={onDeleteClick} destructive>
|
||||
<div>{t("shared.delete")}</div>
|
||||
</ButtonSecondary>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<ButtonSecondary onClick={() => navigate(adminPath("entities/formulas", { mode: "host-aware" }))}>{t("shared.cancel")}</ButtonSecondary>
|
||||
<LoadingButton actionName={getActionName()} type="submit" disabled={navigation.state === "submitting"}>
|
||||
{t("shared.save")}
|
||||
</LoadingButton>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<FormulaComponentModal
|
||||
open={showModal !== undefined}
|
||||
item={showModal?.item}
|
||||
idx={showModal?.idx}
|
||||
order={components.length + 1}
|
||||
onClose={() => setShowModal(undefined)}
|
||||
onSave={(item) => onSave(item)}
|
||||
onRemove={(idx) => {
|
||||
components.splice(idx, 1);
|
||||
setComponents([...components]);
|
||||
}}
|
||||
/>
|
||||
|
||||
<ConfirmModal ref={confirmModalDelete} onYes={onDelete} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user