From 197259a81aee85ac27cb083458234cb387695bba Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Sat, 5 Apr 2025 16:09:51 -0400 Subject: [PATCH 1/2] refactor: enhance vendor and risk components - Refactored SelectAssignee component for improved modularity and reusability across vendor and risk forms. - Updated various forms to utilize the new SelectAssignee component, ensuring consistent UI and functionality. - Improved code formatting and structure for better readability and maintainability. - Added loading states for vendor and risk overview pages to enhance user experience during data fetching. --- .../[evidenceId]/components/ReviewSection.tsx | 232 +++---- .../frameworks/components/FrameworkCard.tsx | 307 +++++----- .../[orgId]/risk/(overview)/RisksTable.tsx | 106 ++-- .../components/table/RiskColumns.tsx | 156 +++-- .../{controls => risk/(overview)}/loading.tsx | 4 - .../[orgId]/risk/(overview)/page.tsx | 13 +- .../[orgId]/risk/[riskId]/page.tsx | 412 ++++++------- .../(dashboard)/[orgId]/settings/page.tsx | 72 +-- .../(overview)/components/VendorColumns.tsx | 164 ++--- .../(overview)/components/VendorsTable.tsx | 103 ++-- .../[orgId]/vendors/(overview)/loading.tsx | 21 + .../[orgId]/vendors/(overview)/page.tsx | 272 +++++---- .../update-secondary-fields-form.tsx | 320 +++++----- .../tasks/create-vendor-task-form.tsx | 361 ++++++----- .../secondary-fields/secondary-fields.tsx | 424 ++++++------- .../components/title/update-task-sheet.tsx | 478 +++++++-------- .../vendors/components/create-vendor-form.tsx | 568 +++++++++--------- .../components/SelectAssignee.tsx | 0 .../organization/delete-organization.tsx | 2 +- .../organization/update-organization-name.tsx | 2 +- .../forms/policies/UpdatePolicyOverview.tsx | 544 ++++++++--------- .../forms/risks/create-risk-form.tsx | 486 +++++++-------- .../components/forms/risks/risk-overview.tsx | 397 ++++++------ packages/ui/src/components/card.tsx | 2 +- 24 files changed, 2754 insertions(+), 2692 deletions(-) rename apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/{controls => risk/(overview)}/loading.tsx (84%) create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/loading.tsx rename apps/app/src/{app/[locale]/(app)/(dashboard)/[orgId] => }/components/SelectAssignee.tsx (100%) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[evidenceId]/components/ReviewSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[evidenceId]/components/ReviewSection.tsx index 16dd811812..09a295e913 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[evidenceId]/components/ReviewSection.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/[evidenceId]/components/ReviewSection.tsx @@ -2,13 +2,13 @@ import { calculateNextReview } from "@/lib/utils/calculate-next-review"; import type { - Departments, - Evidence, - EvidenceStatus, - Frequency, - Member, - User, - User as UserType, + Departments, + Evidence, + EvidenceStatus, + Frequency, + Member, + User, + User as UserType, } from "@comp/db/types"; import { Button } from "@comp/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card"; @@ -21,130 +21,130 @@ import { EvidenceDepartmentSection } from "./EvidenceDepartmentSection"; import { EvidenceFrequencySection } from "./EvidenceFrequencySection"; import { EvidenceNextReviewSection } from "./EvidenceNextReviewSection"; import { EvidenceStatusSection } from "./EvidenceStatusSection"; -import { SelectAssignee } from "../../../components/SelectAssignee"; +import { SelectAssignee } from "@/components/SelectAssignee"; interface ReviewSectionProps { - evidence: Evidence & { - assignee?: - | (Member & { - user: User; - }) - | null; - }; - lastPublishedAt: Date | null; - frequency: Frequency | null; - department: Departments; - currentAssigneeId: string | null | undefined; - assignees: (Member & { - user: UserType; - })[]; + evidence: Evidence & { + assignee?: + | (Member & { + user: User; + }) + | null; + }; + lastPublishedAt: Date | null; + frequency: Frequency | null; + department: Departments; + currentAssigneeId: string | null | undefined; + assignees: (Member & { + user: UserType; + })[]; } export function ReviewSection({ - lastPublishedAt, - frequency: initialFrequency, - department: initialDepartment, - currentAssigneeId, - evidence, - assignees, + lastPublishedAt, + frequency: initialFrequency, + department: initialDepartment, + currentAssigneeId, + evidence, + assignees, }: ReviewSectionProps) { - const reviewInfo = calculateNextReview(lastPublishedAt, initialFrequency); + const reviewInfo = calculateNextReview(lastPublishedAt, initialFrequency); - // State for tracking form values - const [frequency, setFrequency] = useState( - initialFrequency, - ); - const [department, setDepartment] = useState(initialDepartment); - const [assigneeId, setAssigneeId] = useState( - currentAssigneeId || null, - ); - const [status, setStatus] = useState( - evidence.status || "draft", - ); - const [isSaving, setIsSaving] = useState(false); + // State for tracking form values + const [frequency, setFrequency] = useState( + initialFrequency, + ); + const [department, setDepartment] = useState(initialDepartment); + const [assigneeId, setAssigneeId] = useState( + currentAssigneeId || null, + ); + const [status, setStatus] = useState( + evidence.status || "draft", + ); + const [isSaving, setIsSaving] = useState(false); - const { execute: updateDetailsAction } = useAction(updateEvidenceDetails, { - onSuccess: async () => { - toast.success("Evidence details updated successfully"); - setIsSaving(false); - }, - onError: () => { - toast.error("Failed to update evidence details"); - setIsSaving(false); - }, - }); + const { execute: updateDetailsAction } = useAction(updateEvidenceDetails, { + onSuccess: async () => { + toast.success("Evidence details updated successfully"); + setIsSaving(false); + }, + onError: () => { + toast.error("Failed to update evidence details"); + setIsSaving(false); + }, + }); - const handleSaveChanges = () => { - setIsSaving(true); - updateDetailsAction({ - id: evidence.id, - frequency, - department, - assigneeId, - status, - }); - }; + const handleSaveChanges = () => { + setIsSaving(true); + updateDetailsAction({ + id: evidence.id, + frequency, + department, + assigneeId, + status, + }); + }; - const handleDepartmentChange = (value: Departments) => { - setDepartment(value); - }; + const handleDepartmentChange = (value: Departments) => { + setDepartment(value); + }; - const handleStatusChange = (value: EvidenceStatus) => { - setStatus(value); - }; + const handleStatusChange = (value: EvidenceStatus) => { + setStatus(value); + }; - return ( - - - -
-

{evidence.name}

-

- {evidence.description} -

-
-
-
- -
- + return ( + + + +
+

{evidence.name}

+

+ {evidence.description} +

+
+
+
+ +
+ - + - + - + - -
+ +
- -
-
- ); + + + + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx index 682313cace..6ab1c6c698 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/frameworks/components/FrameworkCard.tsx @@ -3,7 +3,7 @@ import { useI18n } from "@/locales/client"; import type { FrameworkInstance } from "@comp/db/types"; import { Badge } from "@comp/ui/badge"; -import { Card, CardContent, CardFooter } from "@comp/ui/card"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@comp/ui/card"; import { cn } from "@comp/ui/cn"; import { Progress } from "@comp/ui/progress"; import { ClipboardCheck, ClipboardList, Clock, TrendingUp } from "lucide-react"; @@ -13,172 +13,171 @@ import { getFrameworkDetails } from "../lib/getFrameworkDetails"; import { FrameworkInstanceWithControls } from "../types"; interface FrameworkCardProps { - frameworkInstance: FrameworkInstanceWithControls; - complianceScore: number; + frameworkInstance: FrameworkInstanceWithControls; + complianceScore: number; } export function FrameworkCard({ - frameworkInstance, - complianceScore, + frameworkInstance, + complianceScore, }: FrameworkCardProps) { - const { orgId } = useParams<{ orgId: string }>(); - const t = useI18n(); + const { orgId } = useParams<{ orgId: string }>(); + const t = useI18n(); - const getComplianceColor = (score: number) => { - if (score >= 80) return "text-green-500"; - if (score >= 50) return "text-yellow-500"; - return "text-red-500"; - }; + const getComplianceColor = (score: number) => { + if (score >= 80) return "text-green-500"; + if (score >= 50) return "text-yellow-500"; + return "text-red-500"; + }; - const getComplianceProgressColor = (score: number) => { - if (score >= 80) return "bg-green-500"; - if (score >= 50) return "bg-yellow-500"; - return "bg-red-500"; - }; + const getComplianceProgressColor = (score: number) => { + if (score >= 80) return "bg-green-500"; + if (score >= 50) return "bg-yellow-500"; + return "bg-red-500"; + }; - const getStatusBadge = (score: number) => { - if (score >= 95) - return { - label: t("common.status.compliant"), - color: - "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400", - }; - if (score >= 80) - return { - label: "Nearly Compliant", - color: - "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400", - }; - if (score >= 50) - return { - label: t("common.status.in_progress"), - color: - "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400", - }; - return { - label: "Needs Attention", - color: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400", - }; - }; + const getStatusBadge = (score: number) => { + if (score >= 95) + return { + label: t("common.status.compliant"), + color: + "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400", + }; + if (score >= 80) + return { + label: "Nearly Compliant", + color: + "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400", + }; + if (score >= 50) + return { + label: t("common.status.in_progress"), + color: + "bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400", + }; + return { + label: "Needs Attention", + color: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400", + }; + }; - const controlsCount = frameworkInstance.controls?.length || 0; - const compliantControlsCount = Math.round( - (complianceScore / 100) * controlsCount, - ); - const inProgressCount = - frameworkInstance.controls?.filter((control) => - control.artifacts.some( - (artifact) => artifact.policy || artifact.evidence, - ), - ).length || 0; - const notStartedCount = controlsCount - inProgressCount; + const controlsCount = frameworkInstance.controls?.length || 0; + const compliantControlsCount = Math.round( + (complianceScore / 100) * controlsCount, + ); + const inProgressCount = + frameworkInstance.controls?.filter((control) => + control.artifacts.some( + (artifact) => artifact.policy || artifact.evidence, + ), + ).length || 0; + const notStartedCount = controlsCount - inProgressCount; - const frameworkDetails = getFrameworkDetails(frameworkInstance.frameworkId); - const statusBadge = getStatusBadge(complianceScore); + const frameworkDetails = getFrameworkDetails(frameworkInstance.frameworkId); + const statusBadge = getStatusBadge(complianceScore); - // Calculate last activity date - use current date as fallback - const lastActivityDate = new Date().toLocaleDateString(); + // Calculate last activity date - use current date as fallback + const lastActivityDate = new Date().toLocaleDateString(); - return ( - - - -
-
-
-
-

- {frameworkDetails.name} -

- - {frameworkDetails.version} - -
-

- {frameworkDetails.description} -

-
- - {statusBadge.label} - -
+ return ( + + + + {frameworkDetails.name} + + {frameworkDetails.version} + + + +
+

+ {frameworkDetails.description} +

+ + {statusBadge.label} + +
+
+
+ + +
+
+
+ + {t("common.status.title")} + + + {complianceScore}% + +
+
+
+
+
+
-
-
- - {t("common.status.title")} - - - {complianceScore}% - -
-
-
-
-
-
- -
-
-
- - - {t("frameworks.controls.title")} - -
-

- {controlsCount} {t("evidence.items")} -

-
-
-
- - - {t("frameworks.controls.statuses.completed")} - -
-

- {compliantControlsCount} / {controlsCount} -

-
-
-
- - - {t("frameworks.controls.statuses.in_progress")} - -
-

- {inProgressCount} / {controlsCount} -

-
-
- - - -
- - {t("common.last_updated")}: {lastActivityDate} -
- {/*
+
+
+
+ + + {t("frameworks.controls.title")} + +
+

+ {controlsCount} {t("evidence.items")} +

+
+
+
+ + + {t("frameworks.controls.statuses.completed")} + +
+

+ {compliantControlsCount} / {controlsCount} +

+
+
+
+ + + {t("frameworks.controls.statuses.in_progress")} + +
+

+ {inProgressCount} / {controlsCount} +

+
+
+ + + +
+ + {t("common.last_updated")}: {lastActivityDate} +
+ {/*
Trend: +5%
*/} -
- - ); + + + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx index c8d9a89b2f..c221559319 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/RisksTable.tsx @@ -7,77 +7,67 @@ import { useDataTable } from "@/hooks/use-data-table"; import { useI18n } from "@/locales/client"; import { useSession } from "@/utils/auth-client"; import type { Member, Risk, User } from "@comp/db/types"; -import { Button } from "@comp/ui/button"; -import { Input } from "@comp/ui/input"; import { ColumnDef } from "@tanstack/react-table"; -import { Plus } from "lucide-react"; import { useQueryState } from "nuqs"; import { useMemo } from "react"; import { columns as getColumns } from "./components/table/RiskColumns"; +import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; // Define the expected structure of a risk with User as assignee export type RiskRow = Risk & { assignee: User | null }; export const RisksTable = ({ - risks, - assignees, + risks, + assignees, }: { - risks: RiskRow[]; - assignees: (Member & { user: User })[]; + risks: RiskRow[]; + assignees: (Member & { user: User })[]; }) => { - const t = useI18n(); - const session = useSession(); - const orgId = session?.data?.session?.activeOrganizationId; - const [_, setOpen] = useQueryState("create-risk-sheet"); + const t = useI18n(); + const session = useSession(); + const orgId = session?.data?.session?.activeOrganizationId; + const [_, setOpen] = useQueryState("create-risk-sheet"); - // Define columns for risks table - const columns = useMemo[]>( - () => getColumns(orgId ?? ""), - [orgId], - ); + // Define columns for risks table + const columns = useMemo[]>( + () => getColumns(orgId ?? ""), + [orgId], + ); - // Set up the risks table - const table = useDataTable({ - data: risks, - columns, - pageCount: Math.ceil(risks.length / 10), - getRowId: (row) => row.id, - initialState: { - sorting: [{ id: "title", desc: false }], - pagination: { - pageSize: 10, - pageIndex: 0, - }, - }, - }); + // Set up the risks table + const table = useDataTable({ + data: risks, + columns, + pageCount: Math.ceil(risks.length / 10), + getRowId: (row) => row.id, + initialState: { + pagination: { + pageSize: 10, + pageIndex: 0, + }, + }, + }); - const [searchTerm, setSearchTerm] = useQueryState("search", { - defaultValue: "", - }); + const [searchTerm, setSearchTerm] = useQueryState("search", { + defaultValue: "", + }); - return ( - <> -
- setSearchTerm(e.target.value)} - className="max-w-sm" - /> -
- - -
-
- row.id} - /> - - - ); + return ( + <> + row.id} + rowClickBasePath={`/${orgId}/risk`} + > + + + + + + + ); }; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx index 011e149ebd..037415ceaf 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/components/table/RiskColumns.tsx @@ -5,70 +5,100 @@ import type { ColumnDef } from "@tanstack/react-table"; import { UserIcon } from "lucide-react"; import Link from "next/link"; import { RiskRow } from "../../RisksTable"; +import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"; export const columns = (orgId: string): ColumnDef[] => [ - { - header: "Risk", - accessorKey: "title", - cell: ({ row }) => { - return ( - - {row.original.title} - - ); - }, - sortingFn: (a, b) => { - return a.original.title.localeCompare(b.original.title); - }, - enableSorting: true, - }, - { - header: "Status", - accessorKey: "status", - cell: ({ row }) => { - return ; - }, - }, - { - header: "Department", - accessorKey: "department", - cell: ({ row }) => { - return ( - - {row.original.department} - - ); - }, - }, - { - header: "Assignee", - accessorKey: "assignee", - cell: ({ row }) => { - if (!row.original.assignee) { - return ( -
-
- -
-

None

-
- ); - } + { + id: "title", + accessorKey: "title", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( + + {row.original.title} + + ); + }, + meta: { + label: "Risk", + placeholder: "Search for a risk...", + variant: "text", + }, + enableColumnFilter: true, + enableSorting: true, + }, + { + id: "status", + accessorKey: "status", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ; + }, + meta: { + label: "Status", + }, + enableColumnFilter: true, + enableSorting: true, + }, + { + id: "department", + accessorKey: "department", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + return ( + + {row.original.department} + + ); + }, + meta: { + label: "Department", + }, + enableColumnFilter: true, + enableSorting: true, + }, + { + id: "assignee", + accessorKey: "assignee", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + if (!row.original.assignee) { + return ( +
+
+ +
+

None

+
+ ); + } - return ( -
- - - - {row.original.assignee.name?.charAt(0) || "?"} - - -

{row.original.assignee.name}

-
- ); - }, - }, + return ( +
+ + + + {row.original.assignee.name?.charAt(0) || "?"} + + +

{row.original.assignee.name}

+
+ ); + }, + meta: { + label: "Assignee", + }, + enableColumnFilter: true, + }, ]; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/loading.tsx similarity index 84% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/loading.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/loading.tsx index b92a5c46ed..393d27755c 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/controls/loading.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/loading.tsx @@ -12,10 +12,6 @@ export default function Loading() { "10rem", "30rem", "10rem", - "10rem", - "6rem", - "6rem", - "6rem", ]} shrinkZero /> diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx index 9a78b8b01d..f590dafc2a 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/(overview)/page.tsx @@ -16,6 +16,7 @@ export default async function RiskRegisterPage({ params, }: { params: Promise<{ + orgId: string; locale: string; search: string; page: number; @@ -25,14 +26,14 @@ export default async function RiskRegisterPage({ assigneeId: string | null; }>; }) { - const { locale, search, page, pageSize, status, department, assigneeId } = + const { orgId, locale, search, page, pageSize, status, department, assigneeId } = await params; setStaticParamsLocale(locale); const t = await getI18n(); const risks = await getRisks({ - search: search || "", + search: search, page: page || 1, pageSize: pageSize || 10, status: status || null, @@ -73,9 +74,13 @@ export default async function RiskRegisterPage({ } return ( - <> + - + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/page.tsx index 633f13778a..7de52f34c5 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/risk/[riskId]/page.tsx @@ -14,230 +14,230 @@ import { headers } from "next/headers"; import { redirect } from "next/navigation"; interface PageProps { - searchParams: Promise<{ - search?: string; - status?: string; - sort?: string; - page?: string; - per_page?: string; - }>; - params: Promise<{ riskId: string; locale: string; orgId: string }>; + searchParams: Promise<{ + search?: string; + status?: string; + sort?: string; + page?: string; + per_page?: string; + }>; + params: Promise<{ riskId: string; locale: string; orgId: string }>; } export default async function RiskPage({ searchParams, params }: PageProps) { - const { riskId, orgId } = await params; - const risk = await getRisk(riskId); - - const assignees = await getAssignees(); - const t = await getI18n(); - - const { - search, - status, - sort, - page = "1", - per_page = "5", - } = await searchParams; - - const columnHeaders = await getServerColumnHeaders(); - const [column, order] = sort?.split(":") ?? []; - const hasFilters = !!(search || status); - const { tasks: loadedTasks, total } = await getTasks({ - riskId, - search, - status: status as TaskStatus, - column, - order, - page: Number.parseInt(page), - per_page: Number.parseInt(per_page), - }); - - if (!risk) { - redirect("/"); - } - - return ( - -
- -
- - -
-
-
- ); + const { riskId, orgId } = await params; + const risk = await getRisk(riskId); + + const assignees = await getAssignees(); + const t = await getI18n(); + + const { + search, + status, + sort, + page = "1", + per_page = "5", + } = await searchParams; + + const columnHeaders = await getServerColumnHeaders(); + const [column, order] = sort?.split(":") ?? []; + const hasFilters = !!(search || status); + const { tasks: loadedTasks, total } = await getTasks({ + riskId, + search, + status: status as TaskStatus, + column, + order, + page: Number.parseInt(page), + per_page: Number.parseInt(per_page), + }); + + if (!risk) { + redirect("/"); + } + + return ( + +
+ +
+ + +
+
+
+ ); } async function getRisk(riskId: string) { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - if (!session || !session.session.activeOrganizationId) { - return null; - } - - const risk = await db.risk.findUnique({ - where: { - id: riskId, - organizationId: session.session.activeOrganizationId, - }, - include: { - assignee: { - include: { - user: true, - }, - }, - }, - }); - - return risk; + const session = await auth.api.getSession({ + headers: await headers(), + }); + + if (!session || !session.session.activeOrganizationId) { + return null; + } + + const risk = await db.risk.findUnique({ + where: { + id: riskId, + organizationId: session.session.activeOrganizationId, + }, + include: { + assignee: { + include: { + user: true, + }, + }, + }, + }); + + return risk; } async function getTasks({ - riskId, - search, - status, - column, - order, - page = 1, - per_page = 10, + riskId, + search, + status, + column, + order, + page = 1, + per_page = 10, }: { - riskId: string; - search?: string; - status?: TaskStatus; - column?: string; - order?: string; - page?: number; - per_page?: number; + riskId: string; + search?: string; + status?: TaskStatus; + column?: string; + order?: string; + page?: number; + per_page?: number; }) { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - if (!session || !session.session.activeOrganizationId) { - return { tasks: [], total: 0 }; - } - - const skip = (page - 1) * per_page; - - const [tasks, total] = await Promise.all([ - db.task - .findMany({ - where: { - relatedId: riskId, - relatedType: "risk", - organizationId: session.session.activeOrganizationId, - AND: [ - search - ? { - OR: [ - { title: { contains: search, mode: "insensitive" } }, - { - description: { contains: search, mode: "insensitive" }, - }, - ], - } - : {}, - status ? { status } : {}, - ], - }, - orderBy: column - ? { - [column]: order === "asc" ? "asc" : "desc", - } - : { - createdAt: "desc", - }, - skip, - take: per_page, - include: { - assignee: { - include: { - user: true, - }, - }, - }, - }) - .then((tasks) => - tasks.map( - (task): RiskTaskType => ({ - id: task.id, - riskId: task.relatedId, - title: task.title, - status: task.status, - dueDate: task.dueDate.toISOString(), - assigneeId: task.assigneeId ?? "", - assignee: { - name: task.assignee?.user.name ?? "", - image: task.assignee?.user.image ?? "", - }, - }), - ), - ), - db.task.count({ - where: { - relatedId: riskId, - relatedType: "risk", - organizationId: session.session.activeOrganizationId, - AND: [ - search - ? { - OR: [ - { title: { contains: search, mode: "insensitive" } }, - { description: { contains: search, mode: "insensitive" } }, - ], - } - : {}, - status ? { status } : {}, - ], - }, - }), - ]); - - return { tasks, total }; + const session = await auth.api.getSession({ + headers: await headers(), + }); + + if (!session || !session.session.activeOrganizationId) { + return { tasks: [], total: 0 }; + } + + const skip = (page - 1) * per_page; + + const [tasks, total] = await Promise.all([ + db.task + .findMany({ + where: { + relatedId: riskId, + relatedType: "risk", + organizationId: session.session.activeOrganizationId, + AND: [ + search + ? { + OR: [ + { title: { contains: search, mode: "insensitive" } }, + { + description: { contains: search, mode: "insensitive" }, + }, + ], + } + : {}, + status ? { status } : {}, + ], + }, + orderBy: column + ? { + [column]: order === "asc" ? "asc" : "desc", + } + : { + createdAt: "desc", + }, + skip, + take: per_page, + include: { + assignee: { + include: { + user: true, + }, + }, + }, + }) + .then((tasks) => + tasks.map( + (task): RiskTaskType => ({ + id: task.id, + riskId: task.relatedId, + title: task.title, + status: task.status, + dueDate: task.dueDate.toISOString(), + assigneeId: task.assigneeId ?? "", + assignee: { + name: task.assignee?.user.name ?? "", + image: task.assignee?.user.image ?? "", + }, + }), + ), + ), + db.task.count({ + where: { + relatedId: riskId, + relatedType: "risk", + organizationId: session.session.activeOrganizationId, + AND: [ + search + ? { + OR: [ + { title: { contains: search, mode: "insensitive" } }, + { description: { contains: search, mode: "insensitive" } }, + ], + } + : {}, + status ? { status } : {}, + ], + }, + }), + ]); + + return { tasks, total }; } async function getAssignees() { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - if (!session || !session.session.activeOrganizationId) { - return []; - } - - const assignees = await db.member.findMany({ - where: { - organizationId: session.session.activeOrganizationId, - role: { - notIn: ["employee"], - }, - }, - include: { - user: true, - }, - }); - - return assignees; + const session = await auth.api.getSession({ + headers: await headers(), + }); + + if (!session || !session.session.activeOrganizationId) { + return []; + } + + const assignees = await db.member.findMany({ + where: { + organizationId: session.session.activeOrganizationId, + role: { + notIn: ["employee"], + }, + }, + include: { + user: true, + }, + }); + + return assignees; } export async function generateMetadata({ - params, + params, }: { - params: Promise<{ locale: string }>; + params: Promise<{ locale: string }>; }): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); + const { locale } = await params; + setStaticParamsLocale(locale); + const t = await getI18n(); - return { - title: t("risk.risk_overview"), - }; + return { + title: t("risk.risk_overview"), + }; } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/page.tsx index 378edb1d4a..f2c95a0161 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/settings/page.tsx @@ -9,53 +9,53 @@ import { setStaticParamsLocale } from "next-international/server"; import { headers } from "next/headers"; export default async function OrganizationSettings({ - params, + params, }: { - params: Promise<{ locale: string }>; + params: Promise<{ locale: string }>; }) { - const { locale } = await params; - setStaticParamsLocale(locale); + const { locale } = await params; + setStaticParamsLocale(locale); - const organization = await organizationDetails(); + const organization = await organizationDetails(); - return ( -
- - -
- ); + return ( +
+ + +
+ ); } export async function generateMetadata({ - params, + params, }: { - params: Promise<{ locale: string }>; + params: Promise<{ locale: string }>; }): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); + const { locale } = await params; + setStaticParamsLocale(locale); + const t = await getI18n(); - return { - title: t("sidebar.settings"), - }; + return { + title: t("sidebar.settings"), + }; } const organizationDetails = cache(async () => { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - if (!session?.session.activeOrganizationId) { - return null; - } - - const organization = await db.organization.findUnique({ - where: { id: session?.session.activeOrganizationId }, - select: { - name: true, - id: true, - }, - }); - - return organization; + const session = await auth.api.getSession({ + headers: await headers(), + }); + + if (!session?.session.activeOrganizationId) { + return null; + } + + const organization = await db.organization.findUnique({ + where: { id: session?.session.activeOrganizationId }, + select: { + name: true, + id: true, + }, + }); + + return organization; }); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx index 9532d28329..62e3b1611f 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorColumns.tsx @@ -5,73 +5,103 @@ import type { ColumnDef } from "@tanstack/react-table"; import Link from "next/link"; import type { VendorRegisterTableRow } from "./VendorsTable"; import { UserIcon } from "lucide-react"; +import { DataTableColumnHeader } from "@/components/data-table/data-table-column-header"; -export const columns = (orgId: string): ColumnDef[] => [ - { - header: "Vendor", - accessorKey: "name", - cell: ({ row }) => { - return ( - - {row.original.name} - - ); - }, - sortingFn: (a, b) => { - return a.original.name.localeCompare(b.original.name); - }, - enableSorting: true, - }, - { - header: "Status", - accessorKey: "status", - cell: ({ row }) => { - return ; - }, - }, - { - header: "Category", - accessorKey: "category", - cell: ({ row }) => { - return ( - - {row.original.category} - - ); - }, - }, - { - header: "Assignee", - accessorKey: "assignee", - cell: ({ row }) => { - // Handle null assignee - if (!row.original.assignee) { - return ( -
-
- -
-

None

-
- ); - } +export const columns: ColumnDef[] = [ + { + id: "name", + accessorKey: "name", + header: ({ column }) => { + return ; + }, + cell: ({ row }) => { + return ( + + {row.original.name} + + ); + }, + meta: { + label: "Vendor Name", + placeholder: "Search for vendor name...", + variant: "text", + }, + enableColumnFilter: true, + }, + { + id: "status", + accessorKey: "status", + header: ({ column }) => { + return ; + }, + cell: ({ row }) => { + return ; + }, + meta: { + label: "Status", + placeholder: "Search by status...", + variant: "select", + }, + }, + { + id: "category", + accessorKey: "category", + header: ({ column }) => { + return ; + }, + cell: ({ row }) => { + return ( + + {row.original.category} + + ); + }, + meta: { + label: "Category", + placeholder: "Search by category...", + variant: "select", + }, + }, + { + id: "assignee", + accessorKey: "assignee", + header: ({ column }) => { + return ; + }, + cell: ({ row }) => { + // Handle null assignee + if (!row.original.assignee) { + return ( +
+
+ +
+

None

+
+ ); + } - return ( -
- - - - {row.original.assignee.user.name?.charAt(0) || "?"} - - -

- {row.original.assignee.user.name} -

-
- ); - }, - }, + return ( +
+ + + + {row.original.assignee.user.name?.charAt(0) || "?"} + + +

+ {row.original.assignee.user.name} +

+
+ ); + }, + meta: { + label: "Assignee", + placeholder: "Search by assignee...", + variant: "select", + }, + }, ]; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx index f122c6ea04..8d3643b370 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/components/VendorsTable.tsx @@ -4,75 +4,62 @@ import { DataTable } from "@/components/data-table/data-table"; import { DataTableSortList } from "@/components/data-table/data-table-sort-list"; import { useDataTable } from "@/hooks/use-data-table"; import { useI18n } from "@/locales/client"; -import { useSession } from "@/utils/auth-client"; import type { Member, User, Vendor } from "@comp/db/types"; -import { Button } from "@comp/ui/button"; -import { Input } from "@comp/ui/input"; -import { Plus } from "lucide-react"; import { useQueryState } from "nuqs"; import { CreateVendorSheet } from "../../components/create-vendor-sheet"; import { columns } from "./VendorColumns"; +import { useParams } from "next/navigation"; +import React from "react"; +import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; export type VendorRegisterTableRow = Vendor & { - assignee: { - user: User; - } | null; + assignee: { + user: User; + } | null; }; export const VendorsTable = ({ - data, - assignees, + data, + assignees, }: { - data: VendorRegisterTableRow[]; - assignees: (Member & { user: User })[]; + data: VendorRegisterTableRow[]; + assignees: (Member & { user: User })[]; }) => { - const t = useI18n(); - const session = useSession(); - const orgId = session?.data?.session?.activeOrganizationId; - const [_, setOpen] = useQueryState("createVendorSheet"); + const t = useI18n(); + const { orgId } = useParams(); - // Set up the vendors table - const table = useDataTable({ - data, - columns: columns(orgId ?? ""), - pageCount: Math.ceil(data.length / 10), - getRowId: (row) => row.id, - initialState: { - sorting: [{ id: "name", desc: false }], - pagination: { - pageSize: 10, - pageIndex: 0, - }, - }, - }); + // Set up the vendors table + const { table } = useDataTable({ + data, + columns, + pageCount: Math.ceil(data.length / 10), + getRowId: (row) => row.id, + initialState: { + pagination: { + pageSize: 10, + pageIndex: 0, + }, + }, + shallow: false, + clearOnDefault: true, + }); - const [searchTerm, setSearchTerm] = useQueryState("search", { - defaultValue: "", - }); - - return ( - <> -
- setSearchTerm(e.target.value)} - className="max-w-sm" - /> -
- - -
-
- row.id} - /> - - - ); + return ( + <> + row.id} + rowClickBasePath={`/${orgId}/vendors`} + > + + + + + + + ); }; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/loading.tsx new file mode 100644 index 0000000000..393d27755c --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/loading.tsx @@ -0,0 +1,21 @@ +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; +import { Suspense } from "react"; + +export default function Loading() { + return ( + + } + /> + ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/page.tsx index 2cacd4da6b..2d3a2672dc 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/(overview)/page.tsx @@ -15,145 +15,157 @@ import { CreateVendorSheet } from "../components/create-vendor-sheet"; import { AppOnboarding } from "@/components/app-onboarding"; export default async function Page({ - searchParams, - params, + searchParams, + params, }: { - searchParams: Promise<{ - createVendorSheet?: string; - page?: string; - pageSize?: string; - status?: string; - department?: string; - assigneeId?: string; - }>; - params: Promise<{ orgId: string }>; + searchParams: Promise<{ + createVendorSheet?: string; + page?: string; + pageSize?: string; + status?: string; + department?: string; + assigneeId?: string; + }>; + params: Promise<{ orgId: string }>; }) { - const t = await getI18n(); - const searchParamsSchema = z.object({ - createVendorSheet: z.string().optional(), - page: z.string().regex(/^\d+$/).transform(Number).optional(), - pageSize: z.string().regex(/^\d+$/).transform(Number).optional(), - status: z.nativeEnum(VendorStatus).optional(), - department: z.nativeEnum(Departments).optional(), - assigneeId: z.string().uuid().optional(), - }); - - const result = searchParamsSchema.safeParse(await searchParams); - const { orgId } = await params; - - if (!result.success) { - console.error("Invalid search params:", result.error); - redirect("/vendors/register"); - } - - const { createVendorSheet, page, pageSize, status, department, assigneeId } = - result.data; - - const session = await auth.api.getSession({ - headers: await headers(), - }); - - if (!session?.session.activeOrganizationId) { - redirect("/onboarding"); - } - - const vendors = await db.vendor.findMany({ - where: { - organizationId: session.session.activeOrganizationId, - ...(status && { status: status }), - ...(department && { department: department }), - ...(assigneeId && { assigneeId: assigneeId }), - }, - include: { - assignee: { - select: { - user: true, - }, - }, - }, - skip: page ? (Number(page) - 1) * Number(pageSize || 10) : 0, - take: Number(pageSize || 10), - }); - - const assignees = await getAssignees(); - - if (vendors.length === 0) { - return ( - <> - - - - ); - } - - return ( - - - - ); + const t = await getI18n(); + const { orgId } = await params; + const vendors = await getVendors(searchParams); + const assignees = await getAssignees(); + + if (vendors.length === 0) { + return ( + <> + + + + ); + } + + return ( + + + + ); } export async function generateMetadata({ - params, + params, }: { - params: Promise<{ locale: string }>; + params: Promise<{ locale: string }>; }): Promise { - const { locale } = await params; - setStaticParamsLocale(locale); - const t = await getI18n(); + const { locale } = await params; + setStaticParamsLocale(locale); + const t = await getI18n(); - return { - title: t("vendors.register.title"), - }; + return { + title: t("vendors.register.title"), + }; } +const getVendors = cache(async ( + searchParams: Promise<{ + createVendorSheet?: string; + page?: string; + pageSize?: string; + status?: string; + department?: string; + assigneeId?: string; + }>, +) => { + const session = await getServersideSession({ + headers: await headers(), + }); + + if (!session?.session.activeOrganizationId) { + return []; + } + const searchParamsSchema = z.object({ + createVendorSheet: z.string().optional(), + page: z.string().regex(/^\d+$/).transform(Number).optional(), + pageSize: z.string().regex(/^\d+$/).transform(Number).optional(), + status: z.nativeEnum(VendorStatus).optional(), + department: z.nativeEnum(Departments).optional(), + assigneeId: z.string().uuid().optional(), + }); + + const result = searchParamsSchema.safeParse(await searchParams); + + if (!result.success) { + console.error("Invalid search params:", result.error); + return []; + } + + const { page, pageSize, status, department, assigneeId } = result.data; + + const vendors = await db.vendor.findMany({ + where: { + organizationId: session.session.activeOrganizationId, + ...(status && { status: status }), + ...(department && { department: department }), + ...(assigneeId && { assigneeId: assigneeId }), + }, + include: { + assignee: { + select: { + user: true, + }, + }, + }, + skip: page ? (Number(page) - 1) * Number(pageSize || 10) : 0, + take: Number(pageSize || 10), + }); + + return vendors; +}); + const getAssignees = cache(async () => { - const { - session: { activeOrganizationId }, - } = await getServersideSession({ - headers: await headers(), - }); - - if (!activeOrganizationId) { - return []; - } - - const assignees = await db.member.findMany({ - where: { - organizationId: activeOrganizationId, - role: { - notIn: ["employee"], - }, - }, - include: { - user: true, - }, - }); - - return assignees; + const { + session: { activeOrganizationId }, + } = await getServersideSession({ + headers: await headers(), + }); + + if (!activeOrganizationId) { + return []; + } + + const assignees = await db.member.findMany({ + where: { + organizationId: activeOrganizationId, + role: { + notIn: ["employee"], + }, + }, + include: { + user: true, + }, + }); + + return assignees; }); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/secondary-fields/update-secondary-fields-form.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/secondary-fields/update-secondary-fields-form.tsx index d7ae7d0b42..2316ce93fd 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/secondary-fields/update-secondary-fields-form.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/secondary-fields/update-secondary-fields-form.tsx @@ -5,19 +5,19 @@ import { useI18n } from "@/locales/client"; import { Member, VendorCategory, type User, type Vendor } from "@comp/db/types"; import { Button } from "@comp/ui/button"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@comp/ui/form"; import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, } from "@comp/ui/select"; import { zodResolver } from "@hookform/resolvers/zod"; import { Loader2 } from "lucide-react"; @@ -25,164 +25,164 @@ import { useAction } from "next-safe-action/hooks"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import type { z } from "zod"; -import { SelectAssignee } from "../../../../components/SelectAssignee"; +import { SelectAssignee } from "@/components/SelectAssignee"; import { updateVendorSchema } from "../../actions/schema"; import { updateVendorAction } from "../../actions/update-vendor-action"; export function UpdateSecondaryFieldsForm({ - vendor, - assignees, + vendor, + assignees, }: { - vendor: Vendor; - assignees: (Member & { user: User })[]; + vendor: Vendor; + assignees: (Member & { user: User })[]; }) { - const t = useI18n(); + const t = useI18n(); - const updateVendor = useAction(updateVendorAction, { - onSuccess: () => { - toast.success(t("vendors.form.update_vendor_success")); - }, - onError: () => { - toast.error(t("vendors.form.update_vendor_error")); - }, - }); + const updateVendor = useAction(updateVendorAction, { + onSuccess: () => { + toast.success(t("vendors.form.update_vendor_success")); + }, + onError: () => { + toast.error(t("vendors.form.update_vendor_error")); + }, + }); - const form = useForm>({ - resolver: zodResolver(updateVendorSchema), - defaultValues: { - id: vendor.id, - name: vendor.name, - description: vendor.description, - assigneeId: vendor.assigneeId, - category: vendor.category, - status: vendor.status, - }, - }); + const form = useForm>({ + resolver: zodResolver(updateVendorSchema), + defaultValues: { + id: vendor.id, + name: vendor.name, + description: vendor.description, + assigneeId: vendor.assigneeId, + category: vendor.category, + status: vendor.status, + }, + }); - const onSubmit = (data: z.infer) => { - // Explicitly set assigneeId to null if it's an empty string (representing "None") - const finalAssigneeId = data.assigneeId === "" ? null : data.assigneeId; + const onSubmit = (data: z.infer) => { + // Explicitly set assigneeId to null if it's an empty string (representing "None") + const finalAssigneeId = data.assigneeId === "" ? null : data.assigneeId; - updateVendor.execute({ - id: data.id, - name: data.name, - description: data.description, - assigneeId: finalAssigneeId, // Use the potentially nulled value - category: data.category, - status: data.status, - }); - }; + updateVendor.execute({ + id: data.id, + name: data.name, + description: data.description, + assigneeId: finalAssigneeId, // Use the potentially nulled value + category: data.category, + status: data.status, + }); + }; - return ( -
- -
- ( - - {t("common.assignee.label")} - - - - - - )} - /> - ( - - {t("vendors.form.vendor_status")} - - - - - - )} - /> - ( - - {t("vendors.form.vendor_category")} - - - - - - )} - /> -
-
- -
-
- - ); + return ( +
+ +
+ ( + + {t("common.assignee.label")} + + + + + + )} + /> + ( + + {t("vendors.form.vendor_status")} + + + + + + )} + /> + ( + + {t("vendors.form.vendor_category")} + + + + + + )} + /> +
+
+ +
+
+ + ); } diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/tasks/create-vendor-task-form.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/tasks/create-vendor-task-form.tsx index 619d3e064f..1a7639fa10 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/tasks/create-vendor-task-form.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/components/tasks/create-vendor-task-form.tsx @@ -6,30 +6,24 @@ import { createVendorTaskSchema } from "../../actions/schema"; import { SelectUser } from "@/components/select-user"; import { useI18n } from "@/locales/client"; import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, } from "@comp/ui/accordion"; import { Button } from "@comp/ui/button"; import { Calendar } from "@comp/ui/calendar"; import { cn } from "@comp/ui/cn"; import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, } from "@comp/ui/form"; import { Input } from "@comp/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@comp/ui/popover"; -import { - Select, - SelectContent, - SelectTrigger, - SelectValue, -} from "@comp/ui/select"; import { Textarea } from "@comp/ui/textarea"; import { zodResolver } from "@hookform/resolvers/zod"; import { format } from "date-fns"; @@ -37,189 +31,188 @@ import { ArrowRightIcon, CalendarIcon } from "lucide-react"; import { useAction } from "next-safe-action/hooks"; import { useParams } from "next/navigation"; import { useQueryState } from "nuqs"; -import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import type { z } from "zod"; -import { SelectAssignee } from "../../../../components/SelectAssignee"; +import { SelectAssignee } from "@/components/SelectAssignee"; import { Member, User } from "@comp/db/types"; export function CreateVendorTaskForm({ - assignees, + assignees, }: { - assignees: (Member & { user: User })[]; + assignees: (Member & { user: User })[]; }) { - const t = useI18n(); + const t = useI18n(); - const [_, setCreateVendorTaskSheet] = useQueryState( - "create-vendor-task-sheet", - ); - const params = useParams<{ vendorId: string }>(); + const [_, setCreateVendorTaskSheet] = useQueryState( + "create-vendor-task-sheet", + ); + const params = useParams<{ vendorId: string }>(); - const createTask = useAction(createVendorTaskAction, { - onSuccess: () => { - toast.success(t("risk.tasks.form.success")); - setCreateVendorTaskSheet(null); - }, - onError: () => { - toast.error(t("risk.tasks.form.error")); - }, - }); + const createTask = useAction(createVendorTaskAction, { + onSuccess: () => { + toast.success(t("risk.tasks.form.success")); + setCreateVendorTaskSheet(null); + }, + onError: () => { + toast.error(t("risk.tasks.form.error")); + }, + }); - const form = useForm>({ - resolver: zodResolver(createVendorTaskSchema), - defaultValues: { - title: "", - description: "", - dueDate: new Date(), - assigneeId: "", - vendorId: params.vendorId, - }, - }); + const form = useForm>({ + resolver: zodResolver(createVendorTaskSchema), + defaultValues: { + title: "", + description: "", + dueDate: new Date(), + assigneeId: "", + vendorId: params.vendorId, + }, + }); - const onSubmit = (data: z.infer) => { - createTask.execute(data); - }; + const onSubmit = (data: z.infer) => { + createTask.execute(data); + }; - return ( -
- -
-
- - - - {t("risk.tasks.form.title")} - - -
- ( - - - {t("risk.tasks.form.task_title")} - - - - - - - )} - /> + return ( + + +
+
+ + + + {t("risk.tasks.form.title")} + + +
+ ( + + + {t("risk.tasks.form.task_title")} + + + + + + + )} + /> - ( - - - {t("risk.tasks.form.description")} - - -