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;