mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-12 07:20:43 +02:00
remove duplicate save buttons
This commit is contained in:
@@ -43,7 +43,18 @@ import {
|
|||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { isModelKeyPattern } from "@server/lib/aiModelKeyMatch";
|
import { isModelKeyPattern } from "@server/lib/aiModelKeyMatch";
|
||||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||||
import { BudgetsEditor } from "@app/components/BudgetsEditor";
|
import {
|
||||||
|
BudgetRowsFields,
|
||||||
|
getBudgetRowsErrors,
|
||||||
|
rowsFromBudgets,
|
||||||
|
saveBudgetRows,
|
||||||
|
type BudgetRow
|
||||||
|
} from "@app/components/BudgetsEditor";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { aiBudgetQueries } from "@app/lib/queries";
|
||||||
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
import {
|
||||||
Asterisk,
|
Asterisk,
|
||||||
@@ -792,6 +803,9 @@ function EditModelCredenza({
|
|||||||
onSave: (item: AiProviderModelListItem) => void;
|
onSave: (item: AiProviderModelListItem) => void;
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const editSchema = useMemo(
|
const editSchema = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -814,12 +828,78 @@ function EditModelCredenza({
|
|||||||
defaultValues: { modelKey: item.modelKey }
|
defaultValues: { modelKey: item.modelKey }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [pendingBudgetRows, setPendingBudgetRows] = useState<BudgetRow[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const [attemptedBudgetsSave, setAttemptedBudgetsSave] = useState(false);
|
||||||
|
const [savingBudgets, setSavingBudgets] = useState(false);
|
||||||
|
|
||||||
|
const budgetScope =
|
||||||
|
item.modelId !== undefined
|
||||||
|
? { type: "model" as const, id: item.modelId }
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const budgetsQuery = useQuery({
|
||||||
|
...aiBudgetQueries.scoped({
|
||||||
|
scope: budgetScope ?? { type: "model", id: -1 }
|
||||||
|
}),
|
||||||
|
enabled: open && budgetScope !== null
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) return;
|
if (!open) return;
|
||||||
form.reset({ modelKey: item.modelKey });
|
form.reset({ modelKey: item.modelKey });
|
||||||
|
setAttemptedBudgetsSave(false);
|
||||||
}, [form, item.clientId, item.modelKey, open]);
|
}, [form, item.clientId, item.modelKey, open]);
|
||||||
|
|
||||||
function handleSubmit(values: EditFormValues) {
|
useEffect(() => {
|
||||||
|
if (!open || !budgetsQuery.data) return;
|
||||||
|
setPendingBudgetRows(rowsFromBudgets(budgetsQuery.data));
|
||||||
|
}, [open, budgetsQuery.data]);
|
||||||
|
|
||||||
|
async function handleSubmit(values: EditFormValues) {
|
||||||
|
const { conflictingKeys, invalidAmountKeys } =
|
||||||
|
getBudgetRowsErrors(pendingBudgetRows);
|
||||||
|
if (conflictingKeys.size > 0 || invalidAmountKeys.size > 0) {
|
||||||
|
setAttemptedBudgetsSave(true);
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("aiBudgetErrorSave"),
|
||||||
|
description: conflictingKeys.size
|
||||||
|
? t("aiBudgetConflictError")
|
||||||
|
: t("aiBudgetInvalidAmountError")
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (budgetScope) {
|
||||||
|
setSavingBudgets(true);
|
||||||
|
try {
|
||||||
|
const existingBudgets = await queryClient.fetchQuery(
|
||||||
|
aiBudgetQueries.scoped({ scope: budgetScope })
|
||||||
|
);
|
||||||
|
await saveBudgetRows({
|
||||||
|
api,
|
||||||
|
orgId,
|
||||||
|
scope: budgetScope,
|
||||||
|
existingBudgets,
|
||||||
|
rows: pendingBudgetRows
|
||||||
|
});
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
aiBudgetQueries.scoped({ scope: budgetScope })
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("aiBudgetErrorSave"),
|
||||||
|
description: formatAxiosError(e, t("aiBudgetErrorSave"))
|
||||||
|
});
|
||||||
|
setSavingBudgets(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSavingBudgets(false);
|
||||||
|
}
|
||||||
|
|
||||||
onSave({
|
onSave({
|
||||||
...item,
|
...item,
|
||||||
modelKey: values.modelKey.trim()
|
modelKey: values.modelKey.trim()
|
||||||
@@ -881,21 +961,27 @@ function EditModelCredenza({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-4 mt-4">
|
<div className="space-y-4 mt-4">
|
||||||
{item.modelId !== undefined ? (
|
{budgetScope ? (
|
||||||
<BudgetsEditor
|
<>
|
||||||
orgId={orgId}
|
<p className="text-sm text-muted-foreground">
|
||||||
scope={{
|
{t(
|
||||||
type: "model",
|
"aiProviderModelsBudgetDescription"
|
||||||
id: item.modelId
|
)}
|
||||||
}}
|
</p>
|
||||||
hideCardHeader={true}
|
<BudgetRowsFields
|
||||||
title={t(
|
rows={pendingBudgetRows}
|
||||||
"aiProviderModelsBudgetTab"
|
onChange={
|
||||||
)}
|
setPendingBudgetRows
|
||||||
description={t(
|
}
|
||||||
"aiProviderModelsBudgetDescription"
|
disabled={
|
||||||
)}
|
budgetsQuery.isLoading ||
|
||||||
/>
|
savingBudgets
|
||||||
|
}
|
||||||
|
attemptedSave={
|
||||||
|
attemptedBudgetsSave
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t(
|
{t(
|
||||||
@@ -914,7 +1000,12 @@ function EditModelCredenza({
|
|||||||
{t("cancel")}
|
{t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</CredenzaClose>
|
</CredenzaClose>
|
||||||
<Button type="submit" form="ai-provider-model-edit-form">
|
<Button
|
||||||
|
type="submit"
|
||||||
|
form="ai-provider-model-edit-form"
|
||||||
|
loading={savingBudgets}
|
||||||
|
disabled={savingBudgets}
|
||||||
|
>
|
||||||
{t("save")}
|
{t("save")}
|
||||||
</Button>
|
</Button>
|
||||||
</CredenzaFooter>
|
</CredenzaFooter>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
import { aiBudgetQueries } from "@app/lib/queries";
|
import { aiBudgetQueries } from "@app/lib/queries";
|
||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import type { AiBudget } from "@server/db";
|
import type { AiBudget } from "@server/db";
|
||||||
|
import type { AxiosInstance } from "axios";
|
||||||
import { Plus, Trash2 } from "lucide-react";
|
import { Plus, Trash2 } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
@@ -52,7 +53,7 @@ export type BudgetRow = {
|
|||||||
period: AiBudgetPeriod;
|
period: AiBudgetPeriod;
|
||||||
};
|
};
|
||||||
|
|
||||||
function rowsFromBudgets(budgets: AiBudget[]): BudgetRow[] {
|
export function rowsFromBudgets(budgets: AiBudget[]): BudgetRow[] {
|
||||||
return budgets.map((budget) => ({
|
return budgets.map((budget) => ({
|
||||||
key: String(budget.budgetId),
|
key: String(budget.budgetId),
|
||||||
budgetId: budget.budgetId,
|
budgetId: budget.budgetId,
|
||||||
@@ -324,6 +325,66 @@ export function BudgetRowsFields({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function saveBudgetRows({
|
||||||
|
api,
|
||||||
|
orgId,
|
||||||
|
scope,
|
||||||
|
existingBudgets,
|
||||||
|
rows
|
||||||
|
}: {
|
||||||
|
api: AxiosInstance;
|
||||||
|
orgId: string;
|
||||||
|
scope: AiBudgetScope;
|
||||||
|
existingBudgets: AiBudget[];
|
||||||
|
rows: Pick<BudgetRow, "budgetId" | "amount" | "unit" | "period">[];
|
||||||
|
}): Promise<void> {
|
||||||
|
const existingById = new Map(
|
||||||
|
existingBudgets.map((budget) => [budget.budgetId, budget])
|
||||||
|
);
|
||||||
|
const currentBudgetIds = new Set(
|
||||||
|
rows
|
||||||
|
.filter((row) => row.budgetId !== undefined)
|
||||||
|
.map((row) => row.budgetId as number)
|
||||||
|
);
|
||||||
|
const bodyField = getAiBudgetScopeBodyField(scope);
|
||||||
|
|
||||||
|
const toDelete = existingBudgets.filter(
|
||||||
|
(budget) => !currentBudgetIds.has(budget.budgetId)
|
||||||
|
);
|
||||||
|
const toCreate = rows.filter((row) => row.budgetId === undefined);
|
||||||
|
const toUpdate = rows.filter((row) => {
|
||||||
|
if (row.budgetId === undefined) return false;
|
||||||
|
const existingBudget = existingById.get(row.budgetId);
|
||||||
|
if (!existingBudget) return false;
|
||||||
|
return (
|
||||||
|
existingBudget.amount !== Number(row.amount) ||
|
||||||
|
existingBudget.unit !== row.unit ||
|
||||||
|
existingBudget.period !== row.period
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
...toDelete.map((budget) =>
|
||||||
|
api.delete(`/ai-budget/${budget.budgetId}`)
|
||||||
|
),
|
||||||
|
...toCreate.map((row) =>
|
||||||
|
api.put(`/org/${orgId}/ai-budget`, {
|
||||||
|
[bodyField]: scope.id,
|
||||||
|
amount: Number(row.amount),
|
||||||
|
unit: row.unit,
|
||||||
|
period: row.period
|
||||||
|
})
|
||||||
|
),
|
||||||
|
...toUpdate.map((row) =>
|
||||||
|
api.post(`/ai-budget/${row.budgetId}`, {
|
||||||
|
amount: Number(row.amount),
|
||||||
|
unit: row.unit,
|
||||||
|
period: row.period
|
||||||
|
})
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
export function BudgetsEditor({
|
export function BudgetsEditor({
|
||||||
scope,
|
scope,
|
||||||
orgId,
|
orgId,
|
||||||
@@ -376,53 +437,14 @@ export function BudgetsEditor({
|
|||||||
|
|
||||||
setSaveLoading(true);
|
setSaveLoading(true);
|
||||||
try {
|
try {
|
||||||
const existing = budgetsQuery.data ?? [];
|
await saveBudgetRows({
|
||||||
const existingById = new Map(
|
api,
|
||||||
existing.map((budget) => [budget.budgetId, budget])
|
orgId,
|
||||||
);
|
scope,
|
||||||
const currentBudgetIds = new Set(
|
existingBudgets: budgetsQuery.data ?? [],
|
||||||
rows
|
rows
|
||||||
.filter((row) => row.budgetId !== undefined)
|
|
||||||
.map((row) => row.budgetId as number)
|
|
||||||
);
|
|
||||||
const bodyField = getAiBudgetScopeBodyField(scope);
|
|
||||||
|
|
||||||
const toDelete = existing.filter(
|
|
||||||
(budget) => !currentBudgetIds.has(budget.budgetId)
|
|
||||||
);
|
|
||||||
const toCreate = rows.filter((row) => row.budgetId === undefined);
|
|
||||||
const toUpdate = rows.filter((row) => {
|
|
||||||
if (row.budgetId === undefined) return false;
|
|
||||||
const existingBudget = existingById.get(row.budgetId);
|
|
||||||
if (!existingBudget) return false;
|
|
||||||
return (
|
|
||||||
existingBudget.amount !== Number(row.amount) ||
|
|
||||||
existingBudget.unit !== row.unit ||
|
|
||||||
existingBudget.period !== row.period
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
...toDelete.map((budget) =>
|
|
||||||
api.delete(`/ai-budget/${budget.budgetId}`)
|
|
||||||
),
|
|
||||||
...toCreate.map((row) =>
|
|
||||||
api.put(`/org/${orgId}/ai-budget`, {
|
|
||||||
[bodyField]: scope.id,
|
|
||||||
amount: Number(row.amount),
|
|
||||||
unit: row.unit,
|
|
||||||
period: row.period
|
|
||||||
})
|
|
||||||
),
|
|
||||||
...toUpdate.map((row) =>
|
|
||||||
api.post(`/ai-budget/${row.budgetId}`, {
|
|
||||||
amount: Number(row.amount),
|
|
||||||
unit: row.unit,
|
|
||||||
period: row.period
|
|
||||||
})
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
|
|
||||||
await queryClient.invalidateQueries(
|
await queryClient.invalidateQueries(
|
||||||
aiBudgetQueries.scoped({ scope })
|
aiBudgetQueries.scoped({ scope })
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
|
|||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
|
import { aiBudgetQueries } from "@app/lib/queries";
|
||||||
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
import type { Role } from "@server/db";
|
import type { Role } from "@server/db";
|
||||||
import type { UpdateRoleBody, UpdateRoleResponse } from "@server/routers/role";
|
import type { UpdateRoleBody, UpdateRoleResponse } from "@server/routers/role";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
@@ -26,6 +28,7 @@ import {
|
|||||||
RoleForm,
|
RoleForm,
|
||||||
type RoleFormValues
|
type RoleFormValues
|
||||||
} from "./RoleForm";
|
} from "./RoleForm";
|
||||||
|
import { saveBudgetRows } from "./BudgetsEditor";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
|
||||||
type EditRoleFormProps = {
|
type EditRoleFormProps = {
|
||||||
@@ -44,6 +47,7 @@ export default function EditRoleForm({
|
|||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const { isPaidUser } = usePaidStatus();
|
const { isPaidUser } = usePaidStatus();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
|
const queryClient = useQueryClient();
|
||||||
const [loading, startTransition] = useTransition();
|
const [loading, startTransition] = useTransition();
|
||||||
|
|
||||||
async function onSubmit(values: RoleFormValues) {
|
async function onSubmit(values: RoleFormValues) {
|
||||||
@@ -83,6 +87,34 @@ export default function EditRoleForm({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (res && res.status === 200) {
|
if (res && res.status === 200) {
|
||||||
|
if (values.budgets) {
|
||||||
|
try {
|
||||||
|
const scope = { type: "role" as const, id: role.roleId };
|
||||||
|
const existingBudgets = await queryClient.fetchQuery(
|
||||||
|
aiBudgetQueries.scoped({ scope })
|
||||||
|
);
|
||||||
|
await saveBudgetRows({
|
||||||
|
api,
|
||||||
|
orgId: role.orgId,
|
||||||
|
scope,
|
||||||
|
existingBudgets,
|
||||||
|
rows: values.budgets
|
||||||
|
});
|
||||||
|
await queryClient.invalidateQueries(
|
||||||
|
aiBudgetQueries.scoped({ scope })
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("aiBudgetErrorSave"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
e,
|
||||||
|
t("aiBudgetErrorSave")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
variant: "default",
|
variant: "default",
|
||||||
title: t("accessRoleUpdated"),
|
title: t("accessRoleUpdated"),
|
||||||
|
|||||||
+50
-50
@@ -30,17 +30,19 @@ import {
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||||
import { PaidFeaturesAlert } from "./PaidFeaturesAlert";
|
import { PaidFeaturesAlert } from "./PaidFeaturesAlert";
|
||||||
import { CheckboxWithLabel } from "./ui/checkbox";
|
import { CheckboxWithLabel } from "./ui/checkbox";
|
||||||
import {
|
import {
|
||||||
BudgetsEditor,
|
|
||||||
BudgetRowsFields,
|
BudgetRowsFields,
|
||||||
getBudgetRowsErrors,
|
getBudgetRowsErrors,
|
||||||
|
rowsFromBudgets,
|
||||||
type BudgetRow
|
type BudgetRow
|
||||||
} from "@app/components/BudgetsEditor";
|
} from "@app/components/BudgetsEditor";
|
||||||
|
import { aiBudgetQueries } from "@app/lib/queries";
|
||||||
import type { AiBudgetPeriod, AiBudgetUnit } from "@app/lib/aiBudgetScope";
|
import type { AiBudgetPeriod, AiBudgetUnit } from "@app/lib/aiBudgetScope";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import type { Role } from "@server/db";
|
import type { Role } from "@server/db";
|
||||||
@@ -90,6 +92,7 @@ function hasOnlyAbsoluteSudoCommands(value: string | undefined): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PendingRoleBudget = {
|
export type PendingRoleBudget = {
|
||||||
|
budgetId?: number;
|
||||||
amount: string;
|
amount: string;
|
||||||
unit: AiBudgetUnit;
|
unit: AiBudgetUnit;
|
||||||
period: AiBudgetPeriod;
|
period: AiBudgetPeriod;
|
||||||
@@ -221,6 +224,19 @@ export function RoleForm({
|
|||||||
);
|
);
|
||||||
const [attemptedBudgetsSave, setAttemptedBudgetsSave] = useState(false);
|
const [attemptedBudgetsSave, setAttemptedBudgetsSave] = useState(false);
|
||||||
|
|
||||||
|
const budgetsQuery = useQuery({
|
||||||
|
...aiBudgetQueries.scoped({
|
||||||
|
scope: { type: "role", id: role?.roleId ?? -1 }
|
||||||
|
}),
|
||||||
|
enabled: variant === "edit" && !!role
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (variant !== "edit" || !budgetsQuery.data) return;
|
||||||
|
setPendingBudgetRows(rowsFromBudgets(budgetsQuery.data));
|
||||||
|
setAttemptedBudgetsSave(false);
|
||||||
|
}, [variant, budgetsQuery.data]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (sshDisabled) {
|
if (sshDisabled) {
|
||||||
form.setValue("allowSsh", false);
|
form.setValue("allowSsh", false);
|
||||||
@@ -271,32 +287,31 @@ export function RoleForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleFormSubmit(values: z.infer<typeof formSchema>) {
|
function handleFormSubmit(values: z.infer<typeof formSchema>) {
|
||||||
if (variant === "create") {
|
const { conflictingKeys, invalidAmountKeys } =
|
||||||
const { conflictingKeys, invalidAmountKeys } =
|
getBudgetRowsErrors(pendingBudgetRows);
|
||||||
getBudgetRowsErrors(pendingBudgetRows);
|
if (conflictingKeys.size > 0 || invalidAmountKeys.size > 0) {
|
||||||
if (conflictingKeys.size > 0 || invalidAmountKeys.size > 0) {
|
setAttemptedBudgetsSave(true);
|
||||||
setAttemptedBudgetsSave(true);
|
toast({
|
||||||
toast({
|
variant: "destructive",
|
||||||
variant: "destructive",
|
title: t("aiBudgetErrorSave"),
|
||||||
title: t("aiBudgetErrorSave"),
|
description: conflictingKeys.size
|
||||||
description: conflictingKeys.size
|
? t("aiBudgetConflictError")
|
||||||
? t("aiBudgetConflictError")
|
: t("aiBudgetInvalidAmountError")
|
||||||
: t("aiBudgetInvalidAmountError")
|
});
|
||||||
});
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return onSubmit({
|
return onSubmit({
|
||||||
...values,
|
...values,
|
||||||
budgets: pendingBudgetRows.map(({ amount, unit, period }) => ({
|
budgets: pendingBudgetRows.map(
|
||||||
|
({ budgetId, amount, unit, period }) => ({
|
||||||
|
budgetId,
|
||||||
amount,
|
amount,
|
||||||
unit,
|
unit,
|
||||||
period
|
period
|
||||||
}))
|
})
|
||||||
});
|
)
|
||||||
}
|
});
|
||||||
|
|
||||||
return onSubmit(values);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTextImportDropHandlers(field: RoleTextImportField) {
|
function getTextImportDropHandlers(field: RoleTextImportField) {
|
||||||
@@ -689,33 +704,18 @@ export function RoleForm({
|
|||||||
|
|
||||||
{/* Inference Budget tab */}
|
{/* Inference Budget tab */}
|
||||||
<div className="space-y-4 mt-4">
|
<div className="space-y-4 mt-4">
|
||||||
{variant === "edit" && role ? (
|
<p className="text-sm text-muted-foreground">
|
||||||
<BudgetsEditor
|
{t("accessRoleInferenceBudgetDescription")}
|
||||||
orgId={role.orgId}
|
</p>
|
||||||
scope={{
|
<BudgetRowsFields
|
||||||
type: "role",
|
rows={pendingBudgetRows}
|
||||||
id: role.roleId
|
onChange={setPendingBudgetRows}
|
||||||
}}
|
disabled={
|
||||||
hideCardHeader={true}
|
variant === "edit" &&
|
||||||
title={t("accessRoleInferenceBudget")}
|
budgetsQuery.isLoading
|
||||||
description={t(
|
}
|
||||||
"accessRoleInferenceBudgetDescription"
|
attemptedSave={attemptedBudgetsSave}
|
||||||
)}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p className="text-sm text-muted-foreground">
|
|
||||||
{t(
|
|
||||||
"accessRoleInferenceBudgetDescription"
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<BudgetRowsFields
|
|
||||||
rows={pendingBudgetRows}
|
|
||||||
onChange={setPendingBudgetRows}
|
|
||||||
attemptedSave={attemptedBudgetsSave}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</HorizontalTabs>
|
</HorizontalTabs>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user