From d3114326bb2d64a7ace03ad959ae20a4c4f79ef8 Mon Sep 17 00:00:00 2001 From: Asis Ferrer Date: Fri, 26 Jun 2026 00:07:07 +0200 Subject: [PATCH] feat(teams): add team tab UI components --- .../people/_components/team.create.form.tsx | 97 ++++++++++++ .../people/_components/team.edit.form.tsx | 143 ++++++++++++++++++ .../people/_components/team.list.table.tsx | 113 ++++++++++++++ .../people/_components/teams.tab.tsx | 34 +++++ 4 files changed, 387 insertions(+) create mode 100644 src/app/(dashboard)/people/_components/team.create.form.tsx create mode 100644 src/app/(dashboard)/people/_components/team.edit.form.tsx create mode 100644 src/app/(dashboard)/people/_components/team.list.table.tsx create mode 100644 src/app/(dashboard)/people/_components/teams.tab.tsx diff --git a/src/app/(dashboard)/people/_components/team.create.form.tsx b/src/app/(dashboard)/people/_components/team.create.form.tsx new file mode 100644 index 0000000..03a7d03 --- /dev/null +++ b/src/app/(dashboard)/people/_components/team.create.form.tsx @@ -0,0 +1,97 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { useRouter } from "next/navigation" +import { useMemo } from "react" +import { useForm } from "react-hook-form" +import { toast } from "sonner" +import { createTeamAction } from "@/actions/team.actions" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" +import type { Dictionary } from "@/i18n/dictionaries" +import { + buildCreateTeamSchema, + type CreateTeamFormType, +} from "@/schemas/team.schema" + +type TeamFormCopy = Dictionary["inventory"]["teams"]["form"] +type TeamSchemaCopy = Dictionary["inventory"]["teams"]["schema"] + +export default function TeamCreateForm({ + formCopy, + schemaCopy, + submitButtonCopy, +}: { + formCopy: TeamFormCopy + schemaCopy: TeamSchemaCopy + submitButtonCopy: SubmitButtonCopy +}) { + const router = useRouter() + const schema = useMemo( + () => buildCreateTeamSchema(schemaCopy), + [schemaCopy], + ) + + const { + register, + handleSubmit, + reset, + setError, + formState: { errors, isSubmitting, isSubmitSuccessful }, + } = useForm({ + resolver: zodResolver(schema), + }) + + const onSubmit = async (formData: CreateTeamFormType) => { + const response = await createTeamAction(formData) + + if (response?.errors) { + Object.entries(response.errors).forEach(([fieldName, messages]) => { + messages.forEach((msg: string) => { + setError(fieldName as keyof CreateTeamFormType, { + type: "server", + message: msg, + }) + toast.error(msg) + }) + }) + return + } + + if (response?.success) { + toast.success(response.message) + reset() + router.refresh() + } + } + + return ( +
+
+ + + {errors.name &&

{errors.name.message}

} +
+ + {formCopy.createSubmit} + +
+ ) +} diff --git a/src/app/(dashboard)/people/_components/team.edit.form.tsx b/src/app/(dashboard)/people/_components/team.edit.form.tsx new file mode 100644 index 0000000..28c6795 --- /dev/null +++ b/src/app/(dashboard)/people/_components/team.edit.form.tsx @@ -0,0 +1,143 @@ +"use client" + +import { zodResolver } from "@hookform/resolvers/zod" +import { Pencil } from "lucide-react" +import { useRouter } from "next/navigation" +import { useMemo, useState } from "react" +import { useForm } from "react-hook-form" +import { toast } from "sonner" +import { updateTeamAction } from "@/actions/team.actions" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + SubmitButton, + type SubmitButtonCopy, +} from "@/components/forms/submitButton" +import type { Dictionary } from "@/i18n/dictionaries" +import { + buildUpdateTeamSchema, + type UpdateTeamFormType, +} from "@/schemas/team.schema" +import type { TeamSummary } from "@/types" + +type TeamFormCopy = Dictionary["inventory"]["teams"]["form"] +type TeamSchemaCopy = Dictionary["inventory"]["teams"]["schema"] +type TeamListCopy = Dictionary["inventory"]["teams"]["list"] + +export default function TeamEditForm({ + team, + formCopy, + schemaCopy, + listCopy, + submitButtonCopy, +}: { + team: TeamSummary + formCopy: TeamFormCopy + schemaCopy: TeamSchemaCopy + listCopy: TeamListCopy + submitButtonCopy: SubmitButtonCopy +}) { + const router = useRouter() + const [open, setOpen] = useState(false) + const schema = useMemo( + () => buildUpdateTeamSchema(schemaCopy), + [schemaCopy], + ) + + const { + register, + handleSubmit, + setError, + formState: { errors, isSubmitting, isSubmitSuccessful }, + } = useForm({ + resolver: zodResolver(schema), + defaultValues: { + id: team.id, + name: team.name, + }, + }) + + const onSubmit = async (formData: UpdateTeamFormType) => { + const response = await updateTeamAction(formData) + + if (response?.errors) { + Object.entries(response.errors).forEach(([fieldName, messages]) => { + messages.forEach((msg: string) => { + setError(fieldName as keyof UpdateTeamFormType, { + type: "server", + message: msg, + }) + toast.error(msg) + }) + }) + return + } + + if (response?.success) { + toast.success(response.message) + setOpen(false) + router.refresh() + } + } + + return ( + + + + + +
+ + {formCopy.updateSubmit} + {team.name} + + +
+ + + {errors.name && ( +

{errors.name.message}

+ )} +
+ + + + + + {formCopy.updateSubmit} + + +
+
+
+ ) +} diff --git a/src/app/(dashboard)/people/_components/team.list.table.tsx b/src/app/(dashboard)/people/_components/team.list.table.tsx new file mode 100644 index 0000000..a9f3e74 --- /dev/null +++ b/src/app/(dashboard)/people/_components/team.list.table.tsx @@ -0,0 +1,113 @@ +"use client" + +import { Trash } from "lucide-react" +import { useRouter } from "next/navigation" +import { useTransition } from "react" +import { toast } from "sonner" +import { deleteTeamAction } from "@/actions/team.actions" +import { Button } from "@/components/ui/button" +import type { Dictionary } from "@/i18n/dictionaries" +import type { TeamSummary } from "@/types" + +import TeamEditForm from "./team.edit.form" + +type TeamFormCopy = Dictionary["inventory"]["teams"]["form"] +type TeamSchemaCopy = Dictionary["inventory"]["teams"]["schema"] +type TeamListCopy = Dictionary["inventory"]["teams"]["list"] +type SubmitButtonCopy = Dictionary["common"]["submitButton"] + +function DeleteTeamButton({ + team, + copy, +}: { + team: TeamSummary + copy: TeamListCopy +}) { + const router = useRouter() + const [isPending, startTransition] = useTransition() + + const handleDelete = (formData: FormData) => { + startTransition(async () => { + const response = await deleteTeamAction(formData) + + if (!response.success && response.errors?.id) { + toast.error(response.errors.id[0]) + return + } + + if (response.success) { + toast.success(response.message) + router.refresh() + } else { + toast.error(response.message ?? copy.actions.delete) + } + }) + } + + return ( +
+ + +
+ ) +} + +export default function TeamListTable({ + teams, + formCopy, + schemaCopy, + listCopy, + submitButtonCopy, +}: { + teams: TeamSummary[] + formCopy: TeamFormCopy + schemaCopy: TeamSchemaCopy + listCopy: TeamListCopy + submitButtonCopy: SubmitButtonCopy +}) { + if (teams.length === 0) { + return
{listCopy.empty}
+ } + + return ( +
+ + + + + + + + + {teams.map((team) => ( + + + + + ))} + +
+ {listCopy.columns.name} + + {listCopy.columns.actions} +
{team.name} + + +
+
+ ) +} diff --git a/src/app/(dashboard)/people/_components/teams.tab.tsx b/src/app/(dashboard)/people/_components/teams.tab.tsx new file mode 100644 index 0000000..0e6548e --- /dev/null +++ b/src/app/(dashboard)/people/_components/teams.tab.tsx @@ -0,0 +1,34 @@ +import PageHeader from "@/components/common/pageheader" +import { getI18n } from "@/i18n/server" +import { listTeamsUseCase } from "@/use-cases/team.use-cases" + +import TeamCreateForm from "./team.create.form" +import TeamListTable from "./team.list.table" + +export default async function TeamsTab() { + const teams = await listTeamsUseCase() + const { dictionary } = await getI18n() + const copy = dictionary.inventory.teams + + return ( +
+ + + +
+ ) +}