edit org label

This commit is contained in:
Fred KISSIE
2026-05-18 22:14:49 +02:00
parent 25c08e7279
commit 7968c4357b
5 changed files with 136 additions and 5 deletions

View File

@@ -265,6 +265,9 @@
"labelCreate": "Create Label", "labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label", "createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization", "createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label", "labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label", "labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.", "labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",

View File

@@ -63,7 +63,7 @@ export function CreateOrgLabelDialog({
return ( return (
<Credenza open={open} onOpenChange={setOpen}> <Credenza open={open} onOpenChange={setOpen}>
<CredenzaContent className="max-w-md"> <CredenzaContent className="md:max-w-md">
<CredenzaHeader> <CredenzaHeader>
<CredenzaTitle>{t("createLabelDialogTitle")}</CredenzaTitle> <CredenzaTitle>{t("createLabelDialogTitle")}</CredenzaTitle>
<CredenzaDescription> <CredenzaDescription>

View File

@@ -0,0 +1,109 @@
"use client";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import type { AxiosResponse } from "axios";
import { useTranslations } from "next-intl";
import { useTransition } from "react";
import {
Credenza,
CredenzaBody,
CredenzaClose,
CredenzaContent,
CredenzaDescription,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle
} from "./Credenza";
import { OrgLabelForm } from "./OrgLabelForm";
import { Button } from "./ui/button";
export type EditOrgLabelDialogProps = {
open: boolean;
setOpen: (val: boolean) => void;
orgId: string;
onSuccess?: () => void;
label: {
name: string;
color: string;
labelId: number;
};
};
export function EditOrgLabelDialog({
open,
setOpen,
orgId,
onSuccess,
label
}: EditOrgLabelDialogProps) {
const t = useTranslations();
const api = createApiClient(useEnvContext());
const [isSubmitting, startTransition] = useTransition();
async function editOrgLabel(data: { name: string; color: string }) {
try {
const res = await api.patch<
AxiosResponse<CreateOrEditLabelResponse>
>(`/org/${orgId}/label/${label.labelId}`, data);
if (res.status === 200) {
setOpen(false);
onSuccess?.();
toast({
title: t("success"),
description: t("labelEditSuccessMessage")
});
}
} catch (e) {
toast({
title: t("error"),
description: formatAxiosError(e, t("errorOccurred")),
variant: "destructive"
});
}
}
return (
<Credenza open={open} onOpenChange={setOpen}>
<CredenzaContent className="md:max-w-md">
<CredenzaHeader>
<CredenzaTitle>{t("editLabelDialogTitle")}</CredenzaTitle>
<CredenzaDescription>
{t("editLabelDialogDescription")}
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody>
<OrgLabelForm
defaultValue={label}
onSubmit={(data) => {
startTransition(async () => editOrgLabel(data));
}}
/>
</CredenzaBody>
<CredenzaFooter>
<CredenzaClose asChild>
<Button
variant="outline"
onClick={() => setOpen(false)}
disabled={isSubmitting}
>
{t("cancel")}
</Button>
</CredenzaClose>
<Button
type="submit"
form="org-label-form"
disabled={isSubmitting}
loading={isSubmitting}
>
{t("labelEdit")}
</Button>
</CredenzaFooter>
</CredenzaContent>
</Credenza>
);
}

View File

@@ -34,9 +34,10 @@ export type LabelFormData = z.infer<typeof labelFormSchema>;
export type OrgLabelFormProps = { export type OrgLabelFormProps = {
onSubmit: (data: LabelFormData) => void; onSubmit: (data: LabelFormData) => void;
defaultValue?: LabelFormData;
}; };
export function OrgLabelForm({ onSubmit }: OrgLabelFormProps) { export function OrgLabelForm({ onSubmit, defaultValue }: OrgLabelFormProps) {
const t = useTranslations(); const t = useTranslations();
const colorValues = Object.values(LABEL_COLORS); const colorValues = Object.values(LABEL_COLORS);
@@ -46,8 +47,8 @@ export function OrgLabelForm({ onSubmit }: OrgLabelFormProps) {
const form = useForm({ const form = useForm({
resolver: zodResolver(labelFormSchema), resolver: zodResolver(labelFormSchema),
defaultValues: { defaultValues: {
name: "", name: defaultValue?.name ?? "",
color: randomColor color: defaultValue?.color ?? randomColor
} }
}); });

View File

@@ -33,6 +33,7 @@ import { getNextSortOrder, getSortDirection } from "@app/lib/sortColumn";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import ConfirmDeleteDialog from "./ConfirmDeleteDialog"; import ConfirmDeleteDialog from "./ConfirmDeleteDialog";
import { CreateOrgLabelDialog } from "./CreateOrgLabelDialog"; import { CreateOrgLabelDialog } from "./CreateOrgLabelDialog";
import { EditOrgLabelDialog } from "./EditOrgLabelDialog";
export type LabelRow = { export type LabelRow = {
labelId: number; labelId: number;
@@ -134,7 +135,14 @@ export default function OrgLabelsTable({
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem>{t("edit")}</DropdownMenuItem> <DropdownMenuItem
onClick={() => {
setSelectedLabel(row.original);
setIsEditModalOpen(true);
}}
>
{t("edit")}
</DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setSelectedLabel(row.original); setSelectedLabel(row.original);
@@ -192,6 +200,16 @@ export default function OrgLabelsTable({
string={selectedLabel.name} string={selectedLabel.name}
title={t("labelDelete")} title={t("labelDelete")}
/> />
<EditOrgLabelDialog
open={isEditModalOpen}
setOpen={setIsEditModalOpen}
orgId={orgId}
onSuccess={() =>
startTransition(() => router.refresh())
}
label={selectedLabel}
/>
</> </>
)} )}