+
0 ? 100 : 0}%`,
+ opacity: 0.7,
+ }}
+ />
+
+
+
+
+
+ Assignee
+ Policy Count
+
+
+
+
+
+ value.split(" ")[0]}
+ fontSize={12}
+ stroke="hsl(var(--muted-foreground))"
+ />
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+ {Object.entries(chartConfig).map(([key, config]) => (
+
+ ))}
+
+
+
+ );
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/components/policy-status-chart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/components/policy-status-chart.tsx
index d897e4d803..17445aa6ed 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/components/policy-status-chart.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/components/policy-status-chart.tsx
@@ -4,259 +4,257 @@ import * as React from "react";
import { Pie, PieChart, Label, Tooltip, ResponsiveContainer } from "recharts";
import {
- Card,
- CardContent,
- CardHeader,
- CardFooter,
- CardTitle,
+ Card,
+ CardContent,
+ CardHeader,
+ CardFooter,
+ CardTitle,
} from "@comp/ui/card";
import {
- type ChartConfig,
- ChartContainer,
- ChartTooltip,
- ChartTooltipContent,
+ type ChartConfig,
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
} from "@comp/ui/chart";
import { useI18n } from "@/locales/client";
import { Badge } from "@comp/ui/badge";
import {
- PieChart as PieChartIcon,
- BarChart as ChartIcon,
- Info,
+ PieChart as PieChartIcon,
+ BarChart as ChartIcon,
+ Info,
} from "lucide-react";
interface PolicyOverviewData {
- totalPolicies: number;
- publishedPolicies: number;
- draftPolicies: number;
- archivedPolicies: number;
- needsReviewPolicies: number;
+ totalPolicies: number;
+ publishedPolicies: number;
+ draftPolicies: number;
+ archivedPolicies: number;
+ needsReviewPolicies: number;
}
interface PolicyStatusChartProps {
- data?: PolicyOverviewData | null;
+ data?: PolicyOverviewData | null;
}
const CHART_COLORS = {
- published: "hsl(var(--chart-positive))", // green
- draft: "hsl(var(--chart-neutral))", // yellow
- archived: "hsl(var(--chart-warning))", // gray
- needs_review: "hsl(var(--chart-destructive))", // red
+ published: "hsl(var(--chart-positive))", // green
+ draft: "hsl(var(--chart-neutral))", // yellow
+ archived: "hsl(var(--chart-warning))", // gray
+ needs_review: "hsl(var(--chart-destructive))", // red
};
// Custom tooltip component for the pie chart
const StatusTooltip = ({ active, payload }: any) => {
- if (active && payload && payload.length) {
- const data = payload[0].payload;
- return (
-
-
{data.name}
-
- Count: {data.value}
-
-
- );
- }
- return null;
+ if (active && payload && payload.length) {
+ const data = payload[0].payload;
+ return (
+
+
{data.name}
+
+ Count: {data.value}
+
+
+ );
+ }
+ return null;
};
export function PolicyStatusChart({ data }: PolicyStatusChartProps) {
- const t = useI18n();
+ const t = useI18n();
- if (!data) {
- return (
-
-
-
-
-
- {t("policies.dashboard.policy_status") || "Policy Status"}
-
-
- Overview
-
-
-
-
-
-
-
-
-
- No policy data available
-
-
-
-
-
-
-
- );
- }
+ if (!data) {
+ return (
+
+
+
+
+ {t("policies.dashboard.policy_status") || "Policy Status"}
+
+
+ Overview
+
+
+
+
+
+
+
+
+
+ No policy data available
+
+
+
+
+
+
+
+ );
+ }
- const chartData = React.useMemo(() => {
- const items = [
- {
- name: t("policies.status.published"),
- value: data.publishedPolicies,
- fill: CHART_COLORS.published,
- },
- {
- name: t("policies.status.draft"),
- value: data.draftPolicies,
- fill: CHART_COLORS.draft,
- },
- {
- name: t("policies.status.archived"),
- value: data.archivedPolicies,
- fill: CHART_COLORS.archived,
- },
- {
- name: t("policies.status.needs_review"),
- value: data.needsReviewPolicies,
- fill: CHART_COLORS.needs_review,
- },
- ];
+ const chartData = React.useMemo(() => {
+ const items = [
+ {
+ name: t("policies.status.published"),
+ value: data.publishedPolicies,
+ fill: CHART_COLORS.published,
+ },
+ {
+ name: t("policies.status.draft"),
+ value: data.draftPolicies,
+ fill: CHART_COLORS.draft,
+ },
+ {
+ name: t("policies.status.archived"),
+ value: data.archivedPolicies,
+ fill: CHART_COLORS.archived,
+ },
+ {
+ name: t("policies.status.needs_review"),
+ value: data.needsReviewPolicies,
+ fill: CHART_COLORS.needs_review,
+ },
+ ];
- return items.filter((item) => item.value);
- }, [data, t]);
+ return items.filter((item) => item.value);
+ }, [data, t]);
- const chartConfig = {
- value: {
- label: "Count",
- },
- } satisfies ChartConfig;
+ const chartConfig = {
+ value: {
+ label: "Count",
+ },
+ } satisfies ChartConfig;
- // Calculate most common status
- const mostCommonStatus = React.useMemo(() => {
- if (!chartData.length) return null;
- return chartData.reduce((prev, current) =>
- prev.value > current.value ? prev : current,
- );
- }, [chartData]);
+ // Calculate most common status
+ const mostCommonStatus = React.useMemo(() => {
+ if (!chartData.length) return null;
+ return chartData.reduce((prev, current) =>
+ prev.value > current.value ? prev : current,
+ );
+ }, [chartData]);
- return (
-
-
-
-
-
- {t("policies.dashboard.policy_status") || "Policy Status"}
-
+ return (
+
+
+
+
+ {t("policies.dashboard.policy_status") || "Policy Status"}
+
- {data.totalPolicies > 0 && mostCommonStatus && (
-
- Most: {mostCommonStatus.name}
-
- )}
-
+ {data.totalPolicies > 0 && mostCommonStatus && (
+
+ Most: {mostCommonStatus.name}
+
+ )}
+
-
-
-
-
-
- } />
-
-
-
-
-
-
-
- {chartData.map((entry) => (
-
-
-
- {entry.name}
-
- ({entry.value})
-
-
-
- ))}
-
-
-
- );
+
+
+
+
+
+ } />
+
+
+
+
+
+
+
+ {chartData.map((entry) => (
+
+
+
+ {entry.name}
+
+ ({entry.value})
+
+
+
+ ))}
+
+
+
+ );
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx
index bdedb621b6..a8d606f717 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/policies/(overview)/page.tsx
@@ -10,170 +10,170 @@ import Loading from "./loading";
import { headers } from "next/headers";
export default async function PoliciesOverview({
- params,
+ params,
}: {
- params: Promise<{ locale: string }>;
+ params: Promise<{ locale: string }>;
}) {
- const { locale } = await params;
- setStaticParamsLocale(locale);
-
- const overview = await getPoliciesOverview();
-
- return (
-
}>
-
-
- );
+ const { locale } = await params;
+ setStaticParamsLocale(locale);
+
+ const overview = await getPoliciesOverview();
+
+ return (
+
}>
+
+
+ );
}
const getPoliciesOverview = async () => {
- const session = await auth.api.getSession({
- headers: await headers(),
- });
-
- if (!session?.session?.activeOrganizationId) {
- return null;
- }
-
- const organizationId = session.session.activeOrganizationId;
-
- return await db.$transaction(async (tx) => {
- const [
- totalPolicies,
- publishedPolicies,
- draftPolicies,
- archivedPolicies,
- needsReviewPolicies,
- policiesByAssignee,
- policiesByAssigneeStatus,
- ] = await Promise.all([
- tx.policy.count({
- where: {
- organizationId,
- },
- }),
- tx.policy.count({
- where: {
- organizationId,
- status: "published",
- isArchived: false,
- },
- }),
- tx.policy.count({
- where: {
- organizationId,
- status: "draft",
- isArchived: false,
- },
- }),
- tx.policy.count({
- where: {
- organizationId,
- isArchived: true,
- },
- }),
- tx.policy.count({
- where: {
- organizationId,
- status: "needs_review",
- isArchived: false,
- },
- }),
- tx.policy.groupBy({
- by: ["assigneeId"],
- _count: true,
- where: {
- organizationId,
- assigneeId: { not: null },
- },
- }),
- tx.policy.findMany({
- where: {
- organizationId,
- assigneeId: { not: null },
- },
- select: {
- status: true,
- isArchived: true,
- assignee: {
- select: {
- id: true,
- user: {
- select: {
- name: true,
- },
- },
- },
- },
- },
- }),
- ]);
-
- // Transform the data for easier consumption by the chart component
- // First group by owner
- const policyDataByOwner = new Map();
-
- for (const policy of policiesByAssigneeStatus) {
- if (!policy.assignee) continue;
-
- const assigneeId = policy.assignee.id;
- if (!policyDataByOwner.has(assigneeId)) {
- policyDataByOwner.set(assigneeId, {
- id: assigneeId,
- name: policy.assignee.user.name || "Unknown",
- total: 0,
- published: 0,
- draft: 0,
- archived: 0,
- needs_review: 0,
- });
- }
-
- const assigneeData = policyDataByOwner.get(assigneeId);
- assigneeData.total += 1;
-
- // Handle archived policies separately
- if (policy.isArchived) {
- assigneeData.archived += 1;
- continue;
- }
-
- // Handle each status type explicitly
- const status = policy.status;
- if (status === "published") assigneeData.published += 1;
- else if (status === "draft") assigneeData.draft += 1;
- else if (status === "needs_review") assigneeData.needs_review += 1;
- }
-
- const assigneeData = Array.from(policyDataByOwner.values());
-
- return {
- totalPolicies,
- publishedPolicies,
- draftPolicies,
- archivedPolicies,
- needsReviewPolicies,
- policiesByAssignee,
- assigneeData,
- };
- });
+ const session = await auth.api.getSession({
+ headers: await headers(),
+ });
+
+ if (!session?.session?.activeOrganizationId) {
+ return null;
+ }
+
+ const organizationId = session.session.activeOrganizationId;
+
+ return await db.$transaction(async (tx) => {
+ const [
+ totalPolicies,
+ publishedPolicies,
+ draftPolicies,
+ archivedPolicies,
+ needsReviewPolicies,
+ policiesByAssignee,
+ policiesByAssigneeStatus,
+ ] = await Promise.all([
+ tx.policy.count({
+ where: {
+ organizationId,
+ },
+ }),
+ tx.policy.count({
+ where: {
+ organizationId,
+ status: "published",
+ isArchived: false,
+ },
+ }),
+ tx.policy.count({
+ where: {
+ organizationId,
+ status: "draft",
+ isArchived: false,
+ },
+ }),
+ tx.policy.count({
+ where: {
+ organizationId,
+ isArchived: true,
+ },
+ }),
+ tx.policy.count({
+ where: {
+ organizationId,
+ status: "needs_review",
+ isArchived: false,
+ },
+ }),
+ tx.policy.groupBy({
+ by: ["assigneeId"],
+ _count: true,
+ where: {
+ organizationId,
+ assigneeId: { not: null },
+ },
+ }),
+ tx.policy.findMany({
+ where: {
+ organizationId,
+ assigneeId: { not: null },
+ },
+ select: {
+ status: true,
+ isArchived: true,
+ assignee: {
+ select: {
+ id: true,
+ user: {
+ select: {
+ name: true,
+ },
+ },
+ },
+ },
+ },
+ }),
+ ]);
+
+ // Transform the data for easier consumption by the chart component
+ // First group by owner
+ const policyDataByOwner = new Map();
+
+ for (const policy of policiesByAssigneeStatus) {
+ if (!policy.assignee) continue;
+
+ const assigneeId = policy.assignee.id;
+ if (!policyDataByOwner.has(assigneeId)) {
+ policyDataByOwner.set(assigneeId, {
+ id: assigneeId,
+ name: policy.assignee.user.name || "Unknown",
+ total: 0,
+ published: 0,
+ draft: 0,
+ archived: 0,
+ needs_review: 0,
+ });
+ }
+
+ const assigneeData = policyDataByOwner.get(assigneeId);
+ assigneeData.total += 1;
+
+ // Handle archived policies separately
+ if (policy.isArchived) {
+ assigneeData.archived += 1;
+ continue;
+ }
+
+ // Handle each status type explicitly
+ const status = policy.status;
+ if (status === "published") assigneeData.published += 1;
+ else if (status === "draft") assigneeData.draft += 1;
+ else if (status === "needs_review") assigneeData.needs_review += 1;
+ }
+
+ const assigneeData = Array.from(policyDataByOwner.values());
+
+ return {
+ totalPolicies,
+ publishedPolicies,
+ draftPolicies,
+ archivedPolicies,
+ needsReviewPolicies,
+ policiesByAssignee,
+ assigneeData,
+ };
+ });
};
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.policies"),
- };
+ return {
+ title: t("sidebar.policies"),
+ };
}
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 (
-
- );
- }
+ {
+ 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 (
+
+ );
+ }
- 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 (
-
- );
- }
+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 (
+
+ );
+ }
- 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 (
-
-
- );
+ return (
+
+
+ );
}
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 (
-
+
+ );
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/secondary-fields/secondary-fields.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/secondary-fields/secondary-fields.tsx
index fa238776e9..7c2778680a 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/secondary-fields/secondary-fields.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/secondary-fields/secondary-fields.tsx
@@ -1,26 +1,26 @@
"use client";
-import { SelectAssignee } from "@/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee";
+import { SelectAssignee } from "@/components/SelectAssignee";
import type { Member, Task, User } from "@comp/db/types";
import { Button } from "@comp/ui/button";
import { Calendar } from "@comp/ui/calendar";
import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card";
import { cn } from "@comp/ui/cn";
import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
} from "@comp/ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "@comp/ui/popover";
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@comp/ui/select";
import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
@@ -33,218 +33,218 @@ import { updateVendorTaskSchema } from "../../../../actions/schema";
import { updateVendorTaskAction } from "../../../../actions/task/update-task-action";
export default function SecondaryFields({
- task,
- assignees,
+ task,
+ assignees,
}: {
- task: Task & { assignee: { user: User } | null };
- assignees: (Member & { user: User })[];
+ task: Task & { assignee: { user: User } | null };
+ assignees: (Member & { user: User })[];
}) {
- return (
-
-
-
-
-
- Task Details
-
-
-
-
-
-
-
-
- );
+ return (
+
+
+
+
+
+ Task Details
+
+
+
+
+
+
+
+
+ );
}
function TaskSecondaryFieldsForm({
- task,
- assignees,
+ task,
+ assignees,
}: {
- task: Task & {
- assignee: { user: User } | null;
- };
- assignees: (Member & { user: User })[];
+ task: Task & {
+ assignee: { user: User } | null;
+ };
+ assignees: (Member & { user: User })[];
}) {
- const updateTask = useAction(updateVendorTaskAction, {
- onSuccess: () => {
- toast.success("Task updated successfully");
- },
- onError: () => {
- toast.error("Failed to update task");
- },
- });
+ const updateTask = useAction(updateVendorTaskAction, {
+ onSuccess: () => {
+ toast.success("Task updated successfully");
+ },
+ onError: () => {
+ toast.error("Failed to update task");
+ },
+ });
- const form = useForm>({
- resolver: zodResolver(updateVendorTaskSchema),
- defaultValues: {
- id: task.id,
- title: task.title,
- description: task.description,
- dueDate: task.dueDate ? new Date(task.dueDate) : undefined,
- status: task.status,
- assigneeId: task.assigneeId || null,
- },
- });
+ const form = useForm>({
+ resolver: zodResolver(updateVendorTaskSchema),
+ defaultValues: {
+ id: task.id,
+ title: task.title,
+ description: task.description,
+ dueDate: task.dueDate ? new Date(task.dueDate) : undefined,
+ status: task.status,
+ assigneeId: task.assigneeId || null,
+ },
+ });
- const onSubmit = (data: z.infer) => {
- updateTask.execute(data);
- };
+ const onSubmit = (data: z.infer) => {
+ updateTask.execute(data);
+ };
- // Function to render status with correct color
- const renderStatus = (status: string) => {
- const getStatusColor = (status: string) => {
- switch (status) {
- case "open":
- return "#ffc107"; // yellow/amber
- case "in_progress":
- return "#0ea5e9"; // blue
- case "completed":
- return "#00DC73"; // green
- case "cancelled":
- return "#64748b"; // gray
- default:
- return "#64748b";
- }
- };
+ // Function to render status with correct color
+ const renderStatus = (status: string) => {
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "open":
+ return "#ffc107"; // yellow/amber
+ case "in_progress":
+ return "#0ea5e9"; // blue
+ case "completed":
+ return "#00DC73"; // green
+ case "cancelled":
+ return "#64748b"; // gray
+ default:
+ return "#64748b";
+ }
+ };
- return (
-
-
-
{status.replace("_", " ")}
-
- );
- };
+ return (
+
+
+
{status.replace("_", " ")}
+
+ );
+ };
- return (
-
+
+ );
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/title/update-task-sheet.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/title/update-task-sheet.tsx
index 276d48d4c0..e3b52a8bab 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/title/update-task-sheet.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/[vendorId]/tasks/[taskId]/components/title/update-task-sheet.tsx
@@ -1,32 +1,32 @@
"use client";
-import { SelectAssignee } from "@/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee";
+import { SelectAssignee } from "@/components/SelectAssignee";
import type { Member, Task, User } from "@comp/db/types";
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,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@comp/ui/select";
import { Textarea } from "@comp/ui/textarea";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -42,241 +42,241 @@ import { updateVendorTaskSchema } from "../../../../actions/schema";
import { updateVendorTaskAction } from "../../../../actions/task/update-task-action";
interface UpdateTaskSheetProps {
- task: Task & { assignee: { user: User } | null };
- assignees: (Member & { user: User })[];
+ task: Task & { assignee: { user: User } | null };
+ assignees: (Member & { user: User })[];
}
export function UpdateTaskSheet({ task, assignees }: UpdateTaskSheetProps) {
- const [_, setTaskOverviewSheet] = useQueryState("task-overview-sheet");
- const params = useParams<{ taskId: string }>();
+ const [_, setTaskOverviewSheet] = useQueryState("task-overview-sheet");
+ const params = useParams<{ taskId: string }>();
- const updateTask = useAction(updateVendorTaskAction, {
- onSuccess: () => {
- toast.success("Task updated successfully");
- setTaskOverviewSheet(null);
- },
- onError: () => {
- toast.error("Failed to update task");
- },
- });
+ const updateTask = useAction(updateVendorTaskAction, {
+ onSuccess: () => {
+ toast.success("Task updated successfully");
+ setTaskOverviewSheet(null);
+ },
+ onError: () => {
+ toast.error("Failed to update task");
+ },
+ });
- const form = useForm>({
- resolver: zodResolver(updateVendorTaskSchema),
- defaultValues: {
- id: params.taskId,
- title: task.title,
- description: task.description,
- dueDate: task.dueDate ? new Date(task.dueDate) : undefined,
- status: task.status,
- assigneeId: task.assigneeId || null,
- },
- });
+ const form = useForm>({
+ resolver: zodResolver(updateVendorTaskSchema),
+ defaultValues: {
+ id: params.taskId,
+ title: task.title,
+ description: task.description,
+ dueDate: task.dueDate ? new Date(task.dueDate) : undefined,
+ status: task.status,
+ assigneeId: task.assigneeId || null,
+ },
+ });
- const onSubmit = (data: z.infer) => {
- updateTask.execute(data);
- };
+ const onSubmit = (data: z.infer) => {
+ updateTask.execute(data);
+ };
- // Function to render status with correct color
- const renderStatus = (status: string) => {
- const getStatusColor = (status: string) => {
- switch (status) {
- case "open":
- return "#ffc107"; // yellow/amber
- case "in_progress":
- return "#0ea5e9"; // blue
- case "completed":
- return "#00DC73"; // green
- case "cancelled":
- return "#64748b"; // gray
- default:
- return "#64748b";
- }
- };
+ // Function to render status with correct color
+ const renderStatus = (status: string) => {
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case "open":
+ return "#ffc107"; // yellow/amber
+ case "in_progress":
+ return "#0ea5e9"; // blue
+ case "completed":
+ return "#00DC73"; // green
+ case "cancelled":
+ return "#64748b"; // gray
+ default:
+ return "#64748b";
+ }
+ };
- return (
-
-
-
{status.replace("_", " ")}
-
- );
- };
+ return (
+
+
+
{status.replace("_", " ")}
+
+ );
+ };
- return (
-
-
-
-
-
-
- Task Details
-
-
-
(
-
- Title
-
-
-
-
-
- )}
- />
+ return (
+
+
+
+
+
+
+ Task Details
+
+
-
-
-
-
+
(
+
+ Assignee
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
-
-
-
-
- );
+
+
+
+
+ );
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/components/create-vendor-form.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/components/create-vendor-form.tsx
index a2732937ab..ad47988ce0 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/components/create-vendor-form.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/vendors/components/create-vendor-form.tsx
@@ -4,27 +4,27 @@ import { useI18n } from "@/locales/client";
import { useSession } from "@/utils/auth-client";
import { Member, User, VendorCategory, VendorStatus } from "@comp/db/types";
import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
} from "@comp/ui/accordion";
import { Button } from "@comp/ui/button";
import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
} from "@comp/ui/form";
import { Input } from "@comp/ui/input";
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@comp/ui/select";
import { Textarea } from "@comp/ui/textarea";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -35,290 +35,290 @@ import { useQueryState } from "nuqs";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
-import { SelectAssignee } from "../../components/SelectAssignee";
+import { SelectAssignee } from "@/components/SelectAssignee";
import { createVendorAction } from "../actions/create-vendor-action";
const createVendorSchema = z.object({
- name: z.string().min(1, "Name is required"),
- website: z.string().url("Must be a valid URL").optional(),
- description: z.string().optional(),
- category: z.nativeEnum(VendorCategory),
- status: z.nativeEnum(VendorStatus).default(VendorStatus.not_assessed),
- assigneeId: z.string().optional(),
+ name: z.string().min(1, "Name is required"),
+ website: z.string().url("Must be a valid URL").optional(),
+ description: z.string().optional(),
+ category: z.nativeEnum(VendorCategory),
+ status: z.nativeEnum(VendorStatus).default(VendorStatus.not_assessed),
+ assigneeId: z.string().optional(),
});
export function CreateVendorForm({
- assignees,
+ assignees,
}: { assignees: (Member & { user: User })[] }) {
- const t = useI18n();
- const session = useSession();
+ const t = useI18n();
+ const session = useSession();
- // Get the same query parameters as the table
- const [search] = useQueryState("search");
- const [page] = useQueryState("page", {
- defaultValue: 1,
- parse: Number.parseInt,
- });
- const [pageSize] = useQueryState("pageSize", {
- defaultValue: 10,
- parse: Number,
- });
- const [status] = useQueryState("status", {
- defaultValue: null,
- parse: (value) => value as VendorStatus | null,
- });
- const [category] = useQueryState("category", {
- defaultValue: null,
- parse: (value) => value as VendorCategory | null,
- });
- const [assigneeId] = useQueryState("assigneeId", {
- defaultValue: null,
- parse: (value) => value,
- });
+ // Get the same query parameters as the table
+ const [search] = useQueryState("search");
+ const [page] = useQueryState("page", {
+ defaultValue: 1,
+ parse: Number.parseInt,
+ });
+ const [pageSize] = useQueryState("pageSize", {
+ defaultValue: 10,
+ parse: Number,
+ });
+ const [status] = useQueryState("status", {
+ defaultValue: null,
+ parse: (value) => value as VendorStatus | null,
+ });
+ const [category] = useQueryState("category", {
+ defaultValue: null,
+ parse: (value) => value as VendorCategory | null,
+ });
+ const [assigneeId] = useQueryState("assigneeId", {
+ defaultValue: null,
+ parse: (value) => value,
+ });
- const router = useRouter();
+ const router = useRouter();
- const createVendor = useAction(createVendorAction, {
- onSuccess: async (data) => {
- const organizationId = session.data?.session.activeOrganizationId;
+ const createVendor = useAction(createVendorAction, {
+ onSuccess: async (data) => {
+ const organizationId = session.data?.session.activeOrganizationId;
- if (!organizationId) {
- toast.error(t("vendors.form.create_vendor_error"));
- return;
- }
+ if (!organizationId) {
+ toast.error(t("vendors.form.create_vendor_error"));
+ return;
+ }
- toast.success(t("vendors.form.create_vendor_success"));
+ toast.success(t("vendors.form.create_vendor_success"));
- router.push(`/${organizationId}/vendors/${data.data?.data?.id}`);
- },
- onError: () => {
- toast.error(t("vendors.form.create_vendor_error"));
- },
- });
+ router.push(`/${organizationId}/vendors/${data.data?.data?.id}`);
+ },
+ onError: () => {
+ toast.error(t("vendors.form.create_vendor_error"));
+ },
+ });
- const form = useForm>({
- resolver: zodResolver(createVendorSchema),
- defaultValues: {
- name: "",
- description: "",
- category: VendorCategory.cloud,
- status: VendorStatus.not_assessed,
- },
- });
+ const form = useForm>({
+ resolver: zodResolver(createVendorSchema),
+ defaultValues: {
+ name: "",
+ description: "",
+ category: VendorCategory.cloud,
+ status: VendorStatus.not_assessed,
+ },
+ });
- const onSubmit = (data: z.infer) => {
- createVendor.execute(data);
- };
+ const onSubmit = (data: z.infer) => {
+ createVendor.execute(data);
+ };
- return (
-
-
-
-
+ return (
+
+
+
+
-
-
-
-
-
-
- );
+
+
+
+
+
+
+ );
}
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee.tsx b/apps/app/src/components/SelectAssignee.tsx
similarity index 100%
rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee.tsx
rename to apps/app/src/components/SelectAssignee.tsx
diff --git a/apps/app/src/components/forms/organization/delete-organization.tsx b/apps/app/src/components/forms/organization/delete-organization.tsx
index a2e67e2fa8..33bcd0eee0 100644
--- a/apps/app/src/components/forms/organization/delete-organization.tsx
+++ b/apps/app/src/components/forms/organization/delete-organization.tsx
@@ -62,7 +62,7 @@ export function DeleteOrganization({
-
+
diff --git a/apps/app/src/components/forms/organization/update-organization-name.tsx b/apps/app/src/components/forms/organization/update-organization-name.tsx
index 1fa0d7eefd..d1e577d3c1 100644
--- a/apps/app/src/components/forms/organization/update-organization-name.tsx
+++ b/apps/app/src/components/forms/organization/update-organization-name.tsx
@@ -90,7 +90,7 @@ export function UpdateOrganizationName({
)}
/>
-
+
{t("settings.general.org_name_tip")}
diff --git a/apps/app/src/components/forms/policies/UpdatePolicyOverview.tsx b/apps/app/src/components/forms/policies/UpdatePolicyOverview.tsx
index 8d0e782086..f3e834af91 100644
--- a/apps/app/src/components/forms/policies/UpdatePolicyOverview.tsx
+++ b/apps/app/src/components/forms/policies/UpdatePolicyOverview.tsx
@@ -2,35 +2,35 @@
import { updatePolicyFormAction } from "@/actions/policies/update-policy-form-action";
import { updatePolicyFormSchema } from "@/actions/schema";
-import { SelectAssignee } from "@/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee";
+import { SelectAssignee } from "@/components/SelectAssignee";
import { StatusPolicies, type StatusType } from "@/components/status-policies";
import { useI18n } from "@/locales/client";
import {
- Departments,
- Frequency,
- Member,
- User,
- type Policy,
- type PolicyStatus,
+ Departments,
+ Frequency,
+ Member,
+ User,
+ type Policy,
+ type PolicyStatus,
} from "@comp/db/types";
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 { Popover, PopoverContent, PopoverTrigger } from "@comp/ui/popover";
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@comp/ui/select";
import { Switch } from "@comp/ui/switch";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -42,270 +42,270 @@ import { toast } from "sonner";
import type { z } from "zod";
const policyStatuses: PolicyStatus[] = [
- "draft",
- "published",
- "needs_review",
+ "draft",
+ "published",
+ "needs_review",
] as const;
export function UpdatePolicyOverview({
- policy,
- assignees,
+ policy,
+ assignees,
}: {
- policy: Policy;
- assignees: (Member & { user: User })[];
+ policy: Policy;
+ assignees: (Member & { user: User })[];
}) {
- const t = useI18n();
+ const t = useI18n();
- const updatePolicyForm = useAction(updatePolicyFormAction, {
- onSuccess: () => {
- toast.success(t("policies.overview.form.update_policy_success"));
- },
- onError: () => {
- toast.error(t("policies.overview.form.update_policy_error"));
- },
- });
+ const updatePolicyForm = useAction(updatePolicyFormAction, {
+ onSuccess: () => {
+ toast.success(t("policies.overview.form.update_policy_success"));
+ },
+ onError: () => {
+ toast.error(t("policies.overview.form.update_policy_error"));
+ },
+ });
- const calculateReviewDate = (): Date => {
- if (!policy.reviewDate) {
- return new Date();
- }
- return new Date(policy.reviewDate);
- };
+ const calculateReviewDate = (): Date => {
+ if (!policy.reviewDate) {
+ return new Date();
+ }
+ return new Date(policy.reviewDate);
+ };
- const reviewDate = calculateReviewDate();
+ const reviewDate = calculateReviewDate();
- const form = useForm>({
- resolver: zodResolver(updatePolicyFormSchema),
- defaultValues: {
- id: policy.id,
- status: policy.status,
- assigneeId: policy.assigneeId ?? "",
- department: policy.department ?? Departments.admin,
- review_frequency: policy.frequency ?? Frequency.monthly,
- review_date: reviewDate,
- isRequiredToSign: policy.isRequiredToSign ? "required" : "not_required",
- },
- });
+ const form = useForm>({
+ resolver: zodResolver(updatePolicyFormSchema),
+ defaultValues: {
+ id: policy.id,
+ status: policy.status,
+ assigneeId: policy.assigneeId ?? "",
+ department: policy.department ?? Departments.admin,
+ review_frequency: policy.frequency ?? Frequency.monthly,
+ review_date: reviewDate,
+ isRequiredToSign: policy.isRequiredToSign ? "required" : "not_required",
+ },
+ });
- const onSubmit = (data: z.infer) => {
- updatePolicyForm.execute({
- id: data.id,
- status: data.status as PolicyStatus,
- assigneeId: data.assigneeId,
- department: data.department,
- review_frequency: data.review_frequency,
- review_date: data.review_date,
- isRequiredToSign: data.isRequiredToSign,
- });
- };
+ const onSubmit = (data: z.infer) => {
+ updatePolicyForm.execute({
+ id: data.id,
+ status: data.status as PolicyStatus,
+ assigneeId: data.assigneeId,
+ department: data.department,
+ review_frequency: data.review_frequency,
+ review_date: data.review_date,
+ isRequiredToSign: data.isRequiredToSign,
+ });
+ };
- return (
-
-
-
-
(
-
- {t("policies.overview.form.status")}
-
-
-
-
-
- )}
- />
- (
-
-
- {t("policies.overview.form.review_frequency")}
-
-
-
-
-
-
- )}
- />
- (
-
-
- {t("policies.overview.form.policy_department")}
-
-
-
+
+
+
+ )}
+ />
+ (
+
+
+ {t("policies.overview.form.policy_assignee")}
+
+
+
+
+
+
+ )}
+ />
+
+
+
(
+
+ {t("policies.overview.form.review_date")}
+
+
+
+
+
+
+
+
+
+ date <= new Date()}
+ initialFocus
+ />
+
+
+
+
+ )}
+ />
+ (
+
+
+ {t("policies.overview.form.signature_requirement")}
+
+
+ {
+ field.onChange(checked ? "required" : "not_required");
+ }}
+ />
+
+
+
+ )}
+ />
+
+
+
+
+
+
+ );
}
diff --git a/apps/app/src/components/forms/risks/create-risk-form.tsx b/apps/app/src/components/forms/risks/create-risk-form.tsx
index a4161c59d4..0fd02322ee 100644
--- a/apps/app/src/components/forms/risks/create-risk-form.tsx
+++ b/apps/app/src/components/forms/risks/create-risk-form.tsx
@@ -2,32 +2,32 @@
import { createRiskAction } from "@/actions/risk/create-risk-action";
import { createRiskSchema } from "@/actions/schema";
-import { SelectAssignee } from "@/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee";
+import { SelectAssignee } from "@/components/SelectAssignee";
import { useI18n } from "@/locales/client";
import type { Member, RiskStatus, User } from "@comp/db/types";
import { Departments, RiskCategory } from "@comp/db/types";
import {
- Accordion,
- AccordionContent,
- AccordionItem,
- AccordionTrigger,
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
} from "@comp/ui/accordion";
import { Button } from "@comp/ui/button";
import {
- Form,
- FormControl,
- FormField,
- FormItem,
- FormLabel,
- FormMessage,
+ Form,
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
} from "@comp/ui/form";
import { Input } from "@comp/ui/input";
import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@comp/ui/select";
import { Textarea } from "@comp/ui/textarea";
import { zodResolver } from "@hookform/resolvers/zod";
@@ -39,240 +39,240 @@ import { toast } from "sonner";
import type { z } from "zod";
export function CreateRisk({
- assignees,
+ assignees,
}: { assignees: (Member & { user: User })[] }) {
- const t = useI18n();
+ const t = useI18n();
- // Get the same query parameters as the table
- const [search] = useQueryState("search");
- const [page] = useQueryState("page", {
- defaultValue: 1,
- parse: Number.parseInt,
- });
- const [pageSize] = useQueryState("pageSize", {
- defaultValue: 10,
- parse: Number,
- });
- const [status] = useQueryState("status", {
- defaultValue: null,
- parse: (value) => value as RiskStatus | null,
- });
- const [department] = useQueryState("department", {
- defaultValue: null,
- parse: (value) => value as Departments | null,
- });
- const [assigneeId] = useQueryState("assigneeId", {
- defaultValue: null,
- parse: (value) => value,
- });
+ // Get the same query parameters as the table
+ const [search] = useQueryState("search");
+ const [page] = useQueryState("page", {
+ defaultValue: 1,
+ parse: Number.parseInt,
+ });
+ const [pageSize] = useQueryState("pageSize", {
+ defaultValue: 10,
+ parse: Number,
+ });
+ const [status] = useQueryState("status", {
+ defaultValue: null,
+ parse: (value) => value as RiskStatus | null,
+ });
+ const [department] = useQueryState("department", {
+ defaultValue: null,
+ parse: (value) => value as Departments | null,
+ });
+ const [assigneeId] = useQueryState("assigneeId", {
+ defaultValue: null,
+ parse: (value) => value,
+ });
- const [_, setCreateRiskSheet] = useQueryState("create-risk-sheet");
+ const [_, setCreateRiskSheet] = useQueryState("create-risk-sheet");
- const createRisk = useAction(createRiskAction, {
- onSuccess: async () => {
- toast.success(t("risk.form.create_risk_success"));
- setCreateRiskSheet(null);
- },
- onError: () => {
- toast.error(t("risk.form.create_risk_error"));
- },
- });
+ const createRisk = useAction(createRiskAction, {
+ onSuccess: async () => {
+ toast.success(t("risk.form.create_risk_success"));
+ setCreateRiskSheet(null);
+ },
+ onError: () => {
+ toast.error(t("risk.form.create_risk_error"));
+ },
+ });
- const form = useForm>({
- resolver: zodResolver(createRiskSchema),
- defaultValues: {
- title: "",
- description: "",
- category: RiskCategory.operations,
- department: Departments.admin,
- assigneeId: null,
- },
- });
+ const form = useForm>({
+ resolver: zodResolver(createRiskSchema),
+ defaultValues: {
+ title: "",
+ description: "",
+ category: RiskCategory.operations,
+ department: Departments.admin,
+ assigneeId: null,
+ },
+ });
- const onSubmit = (data: z.infer) => {
- createRisk.execute(data);
- };
+ const onSubmit = (data: z.infer) => {
+ createRisk.execute(data);
+ };
- return (
-
-
-
-
-
-
-
- {t("risk.form.risk_details")}
-
-
-
-
(
-
- {t("risk.form.risk_title")}
-
-
-
-
-
- )}
- />
- (
-
-
- {t("risk.form.risk_description")}
-
-
-
-
-
-
- )}
- />
- (
-
- {t("risk.form.risk_category")}
-
-
-
-
-
-
- {Object.values(RiskCategory).map((category) => {
- const formattedCategory = category
- .toLowerCase()
- .split("_")
- .map(
- (word) =>
- word.charAt(0).toUpperCase() +
- word.slice(1),
- )
- .join(" ");
- return (
-
- {formattedCategory}
-
- );
- })}
-
-
-
-
-
- )}
- />
- (
-
-
- {t("risk.form.risk_department")}
-
-
-
-
-
-
-
- {Object.values(Departments).map(
- (department) => {
- const formattedDepartment =
- department.toUpperCase();
+ return (
+
+
+
+
+
+
+
+ {t("risk.form.risk_details")}
+
+
+
+ (
+
+ {t("risk.form.risk_title")}
+
+
+
+
+
+ )}
+ />
+ (
+
+
+ {t("risk.form.risk_description")}
+
+
+
+
+
+
+ )}
+ />
+ (
+
+ {t("risk.form.risk_category")}
+
+
+
+
+
+
+ {Object.values(RiskCategory).map((category) => {
+ const formattedCategory = category
+ .toLowerCase()
+ .split("_")
+ .map(
+ (word) =>
+ word.charAt(0).toUpperCase() +
+ word.slice(1),
+ )
+ .join(" ");
+ return (
+
+ {formattedCategory}
+
+ );
+ })}
+
+
+
+
+
+ )}
+ />
+ (
+
+
+ {t("risk.form.risk_department")}
+
+
+
+
+
+
+
+ {Object.values(Departments).map(
+ (department) => {
+ const formattedDepartment =
+ department.toUpperCase();
- return (
-
- {formattedDepartment}
-
- );
- },
- )}
-
-
-
-
-
- )}
- />
- (
-
- {t("common.assignee.label")}
-
-
-
-
-
- )}
- />
-
-
-
-
-
+ return (
+
+ {formattedDepartment}
+
+ );
+ },
+ )}
+
+
+
+
+
+ )}
+ />
+
(
+
+ {t("common.assignee.label")}
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
-
-
-
-
-
-
- );
+
+
+
+
+
+
+ );
}
diff --git a/apps/app/src/components/forms/risks/risk-overview.tsx b/apps/app/src/components/forms/risks/risk-overview.tsx
index 22df4d0d7f..d0f2bc3302 100644
--- a/apps/app/src/components/forms/risks/risk-overview.tsx
+++ b/apps/app/src/components/forms/risks/risk-overview.tsx
@@ -2,33 +2,32 @@
import { updateRiskAction } from "@/actions/risk/update-risk-action";
import { updateRiskSchema } from "@/actions/schema";
-import { SelectAssignee } from "@/app/[locale]/(app)/(dashboard)/[orgId]/components/SelectAssignee";
-import { SelectUser } from "@/components/select-user";
+import { SelectAssignee } from "@/components/SelectAssignee";
import { STATUS_TYPES, Status, type StatusType } from "@/components/status";
import { useI18n } from "@/locales/client";
import {
- Departments,
- Member,
- type Risk,
- RiskCategory,
- RiskStatus,
- type User,
+ Departments,
+ Member,
+ type Risk,
+ RiskCategory,
+ RiskStatus,
+ type User,
} 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";
@@ -39,192 +38,192 @@ import { toast } from "sonner";
import type { z } from "zod";
export function UpdateRiskOverview({
- risk,
- assignees,
+ risk,
+ assignees,
}: {
- risk: Risk;
- assignees: (Member & { user: User })[];
+ risk: Risk;
+ assignees: (Member & { user: User })[];
}) {
- const t = useI18n();
+ const t = useI18n();
- const updateRisk = useAction(updateRiskAction, {
- onSuccess: () => {
- toast.success(t("risk.form.update_risk_success"));
- },
- onError: () => {
- toast.error(t("risk.form.update_risk_error"));
- },
- });
+ const updateRisk = useAction(updateRiskAction, {
+ onSuccess: () => {
+ toast.success(t("risk.form.update_risk_success"));
+ },
+ onError: () => {
+ toast.error(t("risk.form.update_risk_error"));
+ },
+ });
- const form = useForm>({
- resolver: zodResolver(updateRiskSchema),
- defaultValues: {
- id: risk.id,
- title: risk.title ?? "",
- description: risk.description ?? "",
- assigneeId: risk.assigneeId ?? null,
- category: risk.category ?? RiskCategory.operations,
- department: risk.department ?? Departments.admin,
- status: risk.status ?? RiskStatus.open,
- },
- });
+ const form = useForm>({
+ resolver: zodResolver(updateRiskSchema),
+ defaultValues: {
+ id: risk.id,
+ title: risk.title ?? "",
+ description: risk.description ?? "",
+ assigneeId: risk.assigneeId ?? null,
+ category: risk.category ?? RiskCategory.operations,
+ department: risk.department ?? Departments.admin,
+ status: risk.status ?? RiskStatus.open,
+ },
+ });
- const onSubmit = (data: z.infer) => {
- updateRisk.execute({
- id: data.id,
- title: data.title,
- description: data.description,
- assigneeId: data.assigneeId,
- category: data.category,
- department: data.department,
- status: data.status,
- });
- };
+ const onSubmit = (data: z.infer) => {
+ updateRisk.execute({
+ id: data.id,
+ title: data.title,
+ description: data.description,
+ assigneeId: data.assigneeId,
+ category: data.category,
+ department: data.department,
+ status: data.status,
+ });
+ };
- console.log(form.formState.defaultValues);
+ console.log(form.formState.defaultValues);
- return (
-
-
-
-
(
-
- {t("common.assignee.label")}
-
-
-
-
-
- )}
- />
- (
-
- {t("risk.form.risk_status")}
-
-
-
-
- {field.value && (
-
- )}
-
-
-
- {STATUS_TYPES.map((status) => (
-
-
-
- ))}
-
-
-
-
-
- )}
- />
- (
-
- {t("risk.form.risk_category")}
-
-
-
-
-
-
- {Object.values(RiskCategory).map((category) => {
- const formattedCategory = category
- .toLowerCase()
- .split("_")
- .map(
- (word) =>
- word.charAt(0).toUpperCase() + word.slice(1),
- )
- .join(" ");
- return (
-
- {formattedCategory}
-
- );
- })}
-
-
-
-
-
- )}
- />
- (
-
- {t("risk.form.risk_department")}
-
-
-
-
-
-
- {Object.values(Departments).map((department) => {
- const formattedDepartment = department.toUpperCase();
+ return (
+
+
+
+ (
+
+ {t("common.assignee.label")}
+
+
+
+
+
+ )}
+ />
+ (
+
+ {t("risk.form.risk_status")}
+
+
+
+
+ {field.value && (
+
+ )}
+
+
+
+ {STATUS_TYPES.map((status) => (
+
+
+
+ ))}
+
+
+
+
+
+ )}
+ />
+ (
+
+ {t("risk.form.risk_category")}
+
+
+
+
+
+
+ {Object.values(RiskCategory).map((category) => {
+ const formattedCategory = category
+ .toLowerCase()
+ .split("_")
+ .map(
+ (word) =>
+ word.charAt(0).toUpperCase() + word.slice(1),
+ )
+ .join(" ");
+ return (
+
+ {formattedCategory}
+
+ );
+ })}
+
+
+
+
+
+ )}
+ />
+ (
+
+ {t("risk.form.risk_department")}
+
+
+
+
+
+
+ {Object.values(Departments).map((department) => {
+ const formattedDepartment = department.toUpperCase();
- return (
-
- {formattedDepartment}
-
- );
- })}
-
-
-
-
-
- )}
- />
-
-
-
-
-
-
- );
+ return (
+
+ {formattedDepartment}
+
+ );
+ })}
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
+
+ );
}
diff --git a/packages/ui/src/components/card.tsx b/packages/ui/src/components/card.tsx
index f16f6d2e24..c9c755d9db 100644
--- a/packages/ui/src/components/card.tsx
+++ b/packages/ui/src/components/card.tsx
@@ -66,7 +66,7 @@ const CardFooter = React.forwardRef<
>(({ className, ...props }, ref) => (
));