From 1f7344f2e6606cf198225e7c22e8dbb4a98ac112 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 20 May 2026 10:28:26 -0700 Subject: [PATCH] add Tags pages --- .../src/Pages/Tags/components/TagsTable.tsx | 103 ++++++++++++++++ client/src/Pages/Tags/create/index.tsx | 116 ++++++++++++++++++ client/src/Pages/Tags/index.tsx | 72 +++++++++++ 3 files changed, 291 insertions(+) create mode 100644 client/src/Pages/Tags/components/TagsTable.tsx create mode 100644 client/src/Pages/Tags/create/index.tsx create mode 100644 client/src/Pages/Tags/index.tsx diff --git a/client/src/Pages/Tags/components/TagsTable.tsx b/client/src/Pages/Tags/components/TagsTable.tsx new file mode 100644 index 000000000..c2cbd24d3 --- /dev/null +++ b/client/src/Pages/Tags/components/TagsTable.tsx @@ -0,0 +1,103 @@ +import { ActionsMenu, type ActionMenuItem } from "@/Components/actions-menu"; +import Typography from "@mui/material/Typography"; +import type { Header } from "@/Components/design-elements/Table"; +import { Table, Chip } from "@/Components/design-elements"; +import { Pagination } from "@/Components/design-elements/Table"; +import { useClientPagination } from "@/Hooks/useClientPagination"; + +import type { Tag } from "@/Types/Tag"; +import { useNavigate } from "react-router"; +import { useTranslation } from "react-i18next"; +import { useTheme } from "@mui/material"; + +interface TagsTableProps { + tags: Tag[]; + setSelectedTag: Function; +} + +export const TagsTable = ({ tags, setSelectedTag }: TagsTableProps) => { + const navigate = useNavigate(); + const { t } = useTranslation(); + const theme = useTheme(); + const { pagedRows, paginationProps } = useClientPagination(tags); + + const getActions = (tag: Tag): ActionMenuItem[] => { + return [ + { + id: 1, + label: t("pages.common.monitors.actions.configure"), + action: () => { + navigate(`/tags/configure/${tag.id}`); + }, + closeMenu: true, + }, + + { + id: 2, + label: ( + + {t("pages.common.monitors.actions.delete")} + + ), + action: async () => { + setSelectedTag(tag); + }, + closeMenu: true, + }, + ]; + }; + + const getHeaders = () => { + const headers: Header[] = [ + { + id: "name", + content: t("common.table.headers.name"), + render: (row) => { + return {row?.name}; + }, + }, + + { + id: "color", + content: t("pages.tags.table.headers.color"), + render: (row) => { + return {row?.color}; + }, + }, + { + id: "chip", + content: t("pages.tags.table.headers.chip"), + render: (row) => { + return ( + + ); + }, + }, + + { + id: "actions", + content: t("common.table.headers.actions"), + render: (row) => { + return ; + }, + }, + ]; + return headers; + }; + + return ( + <> + { + navigate(`/tags/configure/${row.id}`); + }} + /> + {tags.length > 0 && } + + ); +}; diff --git a/client/src/Pages/Tags/create/index.tsx b/client/src/Pages/Tags/create/index.tsx new file mode 100644 index 000000000..9ce7ca7c4 --- /dev/null +++ b/client/src/Pages/Tags/create/index.tsx @@ -0,0 +1,116 @@ +import { BasePage, ConfigBox } from "@/Components/design-elements"; +import { TextField, Button, ColorInput } from "@/Components/inputs"; +import Stack from "@mui/material/Stack"; +import { useTheme } from "@mui/material/styles"; + +import { useEffect } from "react"; +import { useParams } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useGet, usePost, usePatch } from "@/Hooks/UseApi"; +import type { TagFormData } from "@/Validation/tag"; +import type { Tag } from "@/Types/Tag"; +import { useTranslation } from "react-i18next"; +import { useTagsForm } from "@/Hooks/useTagsForm"; + +const TagsCreatePage = () => { + const { t } = useTranslation(); + const theme = useTheme(); + const navigate = useNavigate(); + const { tagId } = useParams<{ tagId: string }>(); + const isEditMode = Boolean(tagId); + + const { data: existingTag } = useGet(isEditMode ? `/tags/${tagId}` : null); + + const { post, loading: isSubmitting } = usePost(); + const { patch, loading: isPatching } = usePatch(); + + const { schema, defaults } = useTagsForm({ data: existingTag }); + + const form = useForm({ + resolver: zodResolver(schema), + defaultValues: defaults, + }); + + const { control, reset, handleSubmit } = form; + + useEffect(() => { + reset(defaults); + }, [defaults, reset]); + + const onSubmit = async (data: TagFormData) => { + const result = isEditMode + ? await patch(`/tags/${tagId}`, data) + : await post("/tags", data); + if (result) { + navigate("/tags"); + } + }; + + return ( + + ( + + )} + /> + } + /> + ( + + )} + /> + } + /> + + + + + + ); +}; + +export default TagsCreatePage; diff --git a/client/src/Pages/Tags/index.tsx b/client/src/Pages/Tags/index.tsx new file mode 100644 index 000000000..9509cfc4b --- /dev/null +++ b/client/src/Pages/Tags/index.tsx @@ -0,0 +1,72 @@ +import { BasePageWithStates } from "@/Components/design-elements"; +import { TagsTable } from "@/Pages/Tags/components/TagsTable"; +import { Dialog } from "@/Components/inputs"; +import { HeaderCreate } from "@/Components/common"; + +import { useState } from "react"; +import { useGet, useDelete } from "@/Hooks/UseApi"; +import { useTranslation } from "react-i18next"; +import type { Tag } from "@/Types/Tag"; +import { useIsAdmin } from "@/Hooks/useIsAdmin"; + +const TagsPage = () => { + const { t } = useTranslation(); + const isAdmin = useIsAdmin(); + + const [selectedTag, setSelectedTag] = useState(null); + const isDialogOpen = Boolean(selectedTag); + + const { + data: tags, + isLoading, + isValidating, + error, + refetch, + } = useGet("/tags/team", {}, { keepPreviousData: true }); + + const { deleteFn, loading: isDeleting } = useDelete(); + + const handleConfirm = async () => { + if (!selectedTag) return; + await deleteFn(`/tags/${selectedTag.id}`); + setSelectedTag(null); + refetch(); + }; + + const handleCancel = () => { + setSelectedTag(null); + }; + + return ( + + + + + + ); +}; + +export default TagsPage;