Track HiveGrid Cloud planning reference

This commit is contained in:
david committed 2026-06-26 20:48:01 +02:00
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>
);
}