add Tags pages
This commit is contained in:
1 parent
fe74d4b465
commit
1f7344f2e6
3 files changed
+291
No files matched your search
@@ -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: (
|
||||
<Typography color={theme.palette.error.main}>
|
||||
{t("pages.common.monitors.actions.delete")}
|
||||
</Typography>
|
||||
),
|
||||
action: async () => {
|
||||
setSelectedTag(tag);
|
||||
},
|
||||
closeMenu: true,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const getHeaders = () => {
|
||||
const headers: Header<Tag>[] = [
|
||||
{
|
||||
id: "name",
|
||||
content: t("common.table.headers.name"),
|
||||
render: (row) => {
|
||||
return <Typography>{row?.name}</Typography>;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: "color",
|
||||
content: t("pages.tags.table.headers.color"),
|
||||
render: (row) => {
|
||||
return <Typography textTransform={"capitalize"}>{row?.color}</Typography>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "chip",
|
||||
content: t("pages.tags.table.headers.chip"),
|
||||
render: (row) => {
|
||||
return (
|
||||
<Chip
|
||||
text={row.name}
|
||||
color={row.color}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
id: "actions",
|
||||
content: t("common.table.headers.actions"),
|
||||
render: (row) => {
|
||||
return <ActionsMenu items={getActions(row)} />;
|
||||
},
|
||||
},
|
||||
];
|
||||
return headers;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table
|
||||
headers={getHeaders()}
|
||||
data={pagedRows}
|
||||
onRowClick={(row) => {
|
||||
navigate(`/tags/configure/${row.id}`);
|
||||
}}
|
||||
/>
|
||||
{tags.length > 0 && <Pagination {...paginationProps} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<Tag>(isEditMode ? `/tags/${tagId}` : null);
|
||||
|
||||
const { post, loading: isSubmitting } = usePost<TagFormData, Tag>();
|
||||
const { patch, loading: isPatching } = usePatch<TagFormData, Tag>();
|
||||
|
||||
const { schema, defaults } = useTagsForm({ data: existingTag });
|
||||
|
||||
const form = useForm<TagFormData>({
|
||||
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 (
|
||||
<BasePage
|
||||
component="form"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<ConfigBox
|
||||
title={t("pages.tags.form.name.title")}
|
||||
subtitle={t("pages.tags.form.name.description")}
|
||||
rightContent={
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
defaultValue={defaults.name}
|
||||
render={({ field, fieldState }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
type="text"
|
||||
fieldLabel={t("pages.tags.form.name.optionName")}
|
||||
placeholder={t("pages.tags.form.name.placeholder")}
|
||||
fullWidth
|
||||
error={!!fieldState.error}
|
||||
helperText={fieldState.error?.message ?? ""}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<ConfigBox
|
||||
title={t("pages.tags.form.color.title")}
|
||||
subtitle={t("pages.tags.form.color.description")}
|
||||
rightContent={
|
||||
<Controller
|
||||
name="color"
|
||||
control={control}
|
||||
defaultValue={defaults.color}
|
||||
render={({ field }) => (
|
||||
<ColorInput
|
||||
format="hex"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
fieldLabel={t("pages.tags.form.color.optionName")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="flex-end"
|
||||
spacing={theme.spacing(2)}
|
||||
>
|
||||
<Button
|
||||
loading={isSubmitting || isPatching}
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
{t("common.buttons.save")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</BasePage>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagsCreatePage;
|
||||
@@ -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<Tag | null>(null);
|
||||
const isDialogOpen = Boolean(selectedTag);
|
||||
|
||||
const {
|
||||
data: tags,
|
||||
isLoading,
|
||||
isValidating,
|
||||
error,
|
||||
refetch,
|
||||
} = useGet<Tag[]>("/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 (
|
||||
<BasePageWithStates
|
||||
headerKey="tags"
|
||||
page={t("pages.tags.fallback.title")}
|
||||
description={t("pages.tags.fallback.description")}
|
||||
loading={isLoading || isValidating}
|
||||
error={!!error}
|
||||
totalCount={tags?.length ?? 0}
|
||||
actionButtonText={t("pages.tags.fallback.actionButton")}
|
||||
actionLink="/tags/create"
|
||||
>
|
||||
<HeaderCreate
|
||||
path="/tags/create"
|
||||
isLoading={isLoading || isValidating}
|
||||
isAdmin={isAdmin}
|
||||
/>
|
||||
<TagsTable
|
||||
tags={tags ?? []}
|
||||
setSelectedTag={setSelectedTag}
|
||||
/>
|
||||
<Dialog
|
||||
open={isDialogOpen}
|
||||
title={t("common.dialogs.delete.title")}
|
||||
content={t("common.dialogs.delete.description")}
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={handleCancel}
|
||||
loading={isDeleting}
|
||||
/>
|
||||
</BasePageWithStates>
|
||||
);
|
||||
};
|
||||
|
||||
export default TagsPage;
|
||||
Reference in new issue
Block a user