diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/layout.tsx deleted file mode 100644 index 1b3d05dce6..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/layout.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { auth } from "@comp/auth"; -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@comp/ui/secondary-menu"; -import { headers } from "next/headers"; - -export default async function Layout({ - children, -}: { - children: React.ReactNode; -}) { - const t = await getI18n(); - const session = await auth.api.getSession({ - headers: await headers(), - }); - const user = session?.user; - const organizationId = session?.session.activeOrganizationId; - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/loading.tsx deleted file mode 100644 index abe4f9f4b4..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/loading.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; - -export default function Loading() { - return ( - - ); -} - diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/page.tsx deleted file mode 100644 index 1e2575f6c2..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { getI18n } from "@/locales/server"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import type { SearchParams } from "@/types"; -import { searchParamsCache } from "./data/validations"; -import { getValidFilters } from "@/lib/data-table"; -import { getEvidence } from "./data/queries"; -import { EvidenceTable } from "./components/evidence-table"; - -interface PolicyTableProps { - params: Promise<{ locale: string }>; - searchParams: Promise; -} - -export default async function PoliciesPage({ - params, - ...props -}: PolicyTableProps) { - const { locale } = await params; - const searchParams = await props.searchParams; - const search = searchParamsCache.parse(searchParams); - const validFilters = getValidFilters(search.filters); - - setStaticParamsLocale(locale); - - const promises = Promise.all([ - getEvidence({ - ...search, - filters: validFilters, - }), - ]); - - return ( - <> - - - ) -} - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string }>; -}): Promise { - const { locale } = await params; - - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("sidebar.evidence"), - }; -} \ No newline at end of file diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/[id]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/[id]/layout.tsx deleted file mode 100644 index 315f4096a3..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/[id]/layout.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { getI18n } from "@/locales/server"; -import { SecondaryMenu } from "@comp/ui/secondary-menu"; - -interface LayoutProps { - children: React.ReactNode; - params: Promise<{ policyId: string; orgId: string }>; -} - -export default async function Layout({ children, params }: LayoutProps) { - const t = await getI18n(); - const { policyId, orgId } = await params; - - return ( -
- - -
{children}
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/[id]/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/[id]/page.tsx deleted file mode 100644 index 771f5aa40d..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/[id]/page.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { cache } from "react"; -import { PolicyOverview } from "@/components/policies/policy-overview"; -import { getI18n } from "@/locales/server"; -import { db } from "@comp/db"; -import { auth } from "@comp/auth"; -import type { Metadata } from "next"; -import { setStaticParamsLocale } from "next-international/server"; -import { headers } from "next/headers"; - -export default async function PolicyDetails({ - params, -}: { - params: Promise<{ locale: string; policyId: string }>; -}) { - const { locale, policyId } = await params; - setStaticParamsLocale(locale); - const policy = await getPolicy(policyId); - const assignees = await getAssignees(); - - return ( -
- -
- ); -} - -export async function generateMetadata({ - params, -}: { - params: Promise<{ locale: string; policyId: string }>; -}): Promise { - const { locale } = await params; - - setStaticParamsLocale(locale); - const t = await getI18n(); - - return { - title: t("policies.overview.title"), - }; -} - -const getPolicy = cache(async (policyId: string) => { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - const organizationId = session?.session.activeOrganizationId; - - if (!organizationId) { - return null; - } - - const policy = await db.policy.findUnique({ - where: { id: policyId, organizationId }, - }); - - if (!policy) { - return null; - } - - return policy; -}); - -const getAssignees = cache(async () => { - const session = await auth.api.getSession({ - headers: await headers(), - }); - - const organizationId = session?.session.activeOrganizationId; - - if (!organizationId) { - return []; - } - - const assignees = await db.member.findMany({ - where: { - organizationId, - role: { - notIn: ["employee"], - }, - }, - include: { - user: true, - }, - }); - - return assignees; -}); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceList.tsx deleted file mode 100644 index ae84b5cdf4..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceList.tsx +++ /dev/null @@ -1,10 +0,0 @@ -"use client"; - -import { useEvidenceTable } from "../hooks/useEvidenceTableContext"; -import { EvidenceListTable } from "./table/EvidenceListTable"; - -export function EvidenceList() { - const { evidenceTasks } = useEvidenceTable(); - - return ; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceListUIStates.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceListUIStates.tsx deleted file mode 100644 index 93c11e58e7..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceListUIStates.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React from "react"; -import { Alert, AlertDescription, AlertTitle } from "@comp/ui/alert"; -import { AlertCircle, FileQuestion, SearchX } from "lucide-react"; -import { useI18n } from "@/locales/client"; -import { Button } from "@comp/ui/button"; - -interface ErrorStateProps { - error: Error; - onRetry?: () => void; -} - -interface EmptyStateProps { - message?: string; - hasFilters?: boolean; - onClearFilters?: () => void; -} - -/** - * Error state component for the evidence list - */ -export function EvidenceListError({ error, onRetry }: ErrorStateProps) { - const t = useI18n(); - - return ( - - - Error - -

{error.message || "Failed to load evidence tasks"}

- {onRetry && ( - - )} -
-
- ); -} - -/** - * Empty state component for the evidence list - */ -export function EvidenceListEmpty({ - message, - hasFilters = false, - onClearFilters, -}: EmptyStateProps) { - const t = useI18n(); - const defaultMessage = hasFilters - ? "No evidence tasks match your current filters" - : "No evidence tasks found"; - - return ( -
- {hasFilters ? ( - - ) : ( - - )} - -

- {hasFilters ? "No Matches Found" : "No Evidence Tasks"} -

- -

- {message || defaultMessage} -

- - {hasFilters && onClearFilters && ( - - )} -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceSummaryCards.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceSummaryCards.tsx deleted file mode 100644 index 983155656c..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/EvidenceSummaryCards.tsx +++ /dev/null @@ -1,110 +0,0 @@ -"use client"; - -import { Card, CardContent, CardHeader, CardTitle } from "@comp/ui/card"; -import { FileX, FileCheck, Clock, CheckCircle } from "lucide-react"; -import { useEvidenceTasksStats } from "../../hooks/useEvidenceTasksStats"; -import { Skeleton } from "@comp/ui/skeleton"; - -export function EvidenceSummaryCards() { - const { stats, isLoading, error } = useEvidenceTasksStats(); - - // Handle loading state - if (isLoading) { - return ( -
- {["empty", "draft", "review", "uptodate"].map((type) => ( - - ))} -
- ); - } - - // Handle error state - if (error) { - console.error("Error loading evidence task stats:", error); - return ( -
- Error loading statistics. Please try again later. -
- ); - } - - // Handle no data state - if (!stats) { - return null; - } - - return ( -
- - - - Empty - - - -
-
{stats.emptyCount}
- -
-

- Evidence tasks without files or links -

-
-
- - - - - Draft - - - -
-
{stats.draftCount}
- -
-

- Has content but not published -

-
-
- - - - - Needs Review - - - -
-
{stats.needsReviewCount}
- -
-

- Published but past due for review -

-
-
- - - - - Up to Date - - - -
-
- {stats.upToDateCount}/{stats.totalCount} -
- -
-

- Published and current -

-
-
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/components/evidence-table-columns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/evidence-table-columns.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/components/evidence-table-columns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/evidence-table-columns.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/components/evidence-table.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/evidence-table.tsx similarity index 91% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/components/evidence-table.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/evidence-table.tsx index a4be48faca..9793d06064 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/components/evidence-table.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/evidence-table.tsx @@ -9,6 +9,7 @@ import { DataTableSortList } from "@/components/data-table/data-table-sort-list" import { getEvidenceColumns } from "./evidence-table-columns"; import { DataTableToolbar } from "@/components/data-table/data-table-toolbar"; import { CreatePolicySheet } from "@/components/sheets/create-policy-sheet"; +import { useParams } from "next/navigation"; interface EvidenceTableProps { promises: Promise<[ @@ -18,6 +19,7 @@ interface EvidenceTableProps { export function EvidenceTable({ promises }: EvidenceTableProps) { const [{ data, pageCount }] = React.use(promises); + const { orgId } = useParams(); const columns = React.useMemo(() => getEvidenceColumns(), []); @@ -38,7 +40,7 @@ export function EvidenceTable({ promises }: EvidenceTableProps) { row.id} - rowClickBasePath="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/evidence/all" + rowClickBasePath={`/${orgId}/evidence`} > diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/index.ts deleted file mode 100644 index 81ee91ca32..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { EvidenceList } from "./EvidenceList"; -export * from "./table/EvidenceFilters"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceFilters/ActiveFilterBadges.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceFilters/ActiveFilterBadges.tsx deleted file mode 100644 index 1dbd3c23a3..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceFilters/ActiveFilterBadges.tsx +++ /dev/null @@ -1,97 +0,0 @@ -"use client"; - -import { Badge } from "@comp/ui/badge"; -import { X } from "lucide-react"; -import { useEvidenceTable } from "../../../hooks/useEvidenceTableContext"; - -export function ActiveFilterBadges() { - const { - status, - setStatus, - frequency, - setFrequency, - department, - setDepartment, - assigneeId, - setAssigneeId, - relevance, - setRelevance, - setPage, - assignees, - } = useEvidenceTable(); - - // Find the assignee name if there's an active assignee filter - const assigneeName = assigneeId - ? assignees.find((a) => a.id === assigneeId)?.name || "Unknown" - : null; - - // Only render if there are active filters - if (!status && !frequency && !department && !assigneeId && !relevance) { - return null; - } - - return ( -
- {status && ( - - Status: {status} - { - setStatus(null); - setPage("1"); - }} - /> - - )} - {relevance && ( - - Relevance: {relevance === "relevant" ? "Relevant" : "Not Relevant"} - { - setRelevance(null); - setPage("1"); - }} - /> - - )} - {frequency && ( - - Frequency: {frequency} - { - setFrequency(null); - setPage("1"); - }} - /> - - )} - {department && ( - - Department: {department.replace(/_/g, " ").toUpperCase()} - { - setDepartment(null); - setPage("1"); - }} - /> - - )} - {assigneeId && assigneeName && ( - - Assignee: {assigneeName} - { - setAssigneeId(null); - setPage("1"); - }} - /> - - )} -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceFilters/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceFilters/index.ts deleted file mode 100644 index 15b35e6f3e..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceFilters/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { ActiveFilterBadges } from "./ActiveFilterBadges"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListColumns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListColumns.tsx deleted file mode 100644 index f4abe6290c..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListColumns.tsx +++ /dev/null @@ -1,184 +0,0 @@ -"use client"; - -import { calculateNextReview } from "@/lib/utils/calculate-next-review"; -import { Avatar, AvatarFallback, AvatarImage } from "@comp/ui/avatar"; -import { Badge } from "@comp/ui/badge"; -import { cn } from "@comp/ui/cn"; -import type { ColumnDef } from "@tanstack/react-table"; -import { format } from "date-fns"; -import { EVIDENCE_STATUS_HEX_COLORS } from "../../../(overview)/constants/evidence-status"; -import type { EvidenceTaskRow } from "../../types"; - -export const EvidenceListColumns: ColumnDef[] = [ - { - id: "name", - accessorKey: "name", - header: "Name", - enableResizing: true, - enableSorting: true, - size: 100, - minSize: 200, - cell: ({ row }) => ( -
- {row.original.name} -
-
-
-
-
-
-
-
- ), - }, - { - id: "status", - accessorKey: "published", - header: "Status", - enableResizing: true, - enableSorting: false, - size: 150, - minSize: 120, - cell: ({ row }) => { - const status = row.original.status; - - return ( -
-
-
- {status - ?.split("_") - .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) - .join(" ")} -
-
- ); - }, - }, - { - id: "department", - accessorKey: "department", - header: "Department", - size: 150, - enableResizing: true, - minSize: 130, - enableSorting: false, - cell: ({ row }) => { - const department = row.original.department; - - if (!department || department === "none") { - return ( - - None - - ); - } - - return ( -
- - {department.replace(/_/g, " ").toUpperCase()} - -
- ); - }, - }, - { - id: "frequency", - accessorKey: "frequency", - header: "Frequency", - size: 150, - enableResizing: true, - minSize: 130, - enableSorting: false, - cell: ({ row }) => { - const frequency = row.original.frequency; - if (!frequency) return null; - - return
{frequency}
; - }, - }, - { - id: "nextReviewDate", - accessorKey: "nextReviewDate", - header: "Next Review Date", - size: 150, - enableResizing: true, - minSize: 180, - enableSorting: false, - cell: ({ row }) => { - if (row.original.lastPublishedAt === null) { - return ( -
- ASAP -
- ); - } - - const reviewInfo = calculateNextReview( - row.original.lastPublishedAt, - row.original.frequency, - ); - - if (!reviewInfo) return null; - - return ( - - ); - }, - }, - { - id: "assignee", - accessorKey: "assignee", - header: "Assignee", - enableResizing: true, - enableSorting: false, - size: 150, - minSize: 150, - cell: ({ row }) => { - const assignee = row.original.assignee; - - if (!assignee) { - return ( -
- Unassigned -
- ); - } - - return ( -
- - - - {assignee.user.name ? assignee.user.name.charAt(0) : "?"} - - - {assignee.user.name} -
- ); - }, - }, -]; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListHeader.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListHeader.tsx deleted file mode 100644 index eddb782be0..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListHeader.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import type { Table } from "@tanstack/react-table"; -import { TableHead, TableHeader, TableRow } from "@comp/ui/table"; -import { flexRender } from "@tanstack/react-table"; -import type { EvidenceTaskRow } from "../../types"; -import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react"; -import { cn } from "@comp/ui/cn"; - -interface EvidenceListHeaderProps { - table: Table; -} - -export function EvidenceListHeader({ table }: EvidenceListHeaderProps) { - return ( - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - {header.isPlaceholder ? null : ( -
- {flexRender( - header.column.columnDef.header, - header.getContext(), - )} - {{ - asc: , - desc: , - }[header.column.getIsSorted() as string] ?? - (header.column.getCanSort() && ( - - ))} -
- )} -
- - ))} - - ))} - - ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListTable.tsx deleted file mode 100644 index 4ede9d928c..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/EvidenceListTable.tsx +++ /dev/null @@ -1,118 +0,0 @@ -"use client"; - -import { DataTable } from "@/components/ui/data-table"; -import { EvidenceListColumns } from "./EvidenceListColumns"; -import type { EvidenceTaskRow } from "../../types"; -import { useParams, useRouter } from "next/navigation"; -import { useEvidenceTable } from "../../hooks/useEvidenceTableContext"; -import { getFilterCategories } from "./components/filterCategories"; -import { useEffect, useMemo, useState } from "react"; - -export function EvidenceListTable({ data }: { data: EvidenceTaskRow[] }) { - const router = useRouter(); - const { orgId } = useParams<{ orgId: string }>(); - const [isMobile, setIsMobile] = useState(false); - - useEffect(() => { - const checkIfMobile = () => { - setIsMobile(window.innerWidth < 768); - }; - - // Initial check - checkIfMobile(); - - // Add event listener - window.addEventListener("resize", checkIfMobile); - - // Cleanup - return () => window.removeEventListener("resize", checkIfMobile); - }, []); - - const { - page, - setPage, - pageSize, - setPageSize, - pagination, - search, - setSearch, - status, - setStatus, - frequency, - setFrequency, - department, - setDepartment, - assigneeId, - setAssigneeId, - frequencies, - departments, - assignees, - hasActiveFilters, - clearFilters, - isLoading, - isSearching, - } = useEvidenceTable(); - - const handleRowClick = (evidenceId: string) => { - router.push(`/${orgId}/evidence/${evidenceId}`); - }; - - const activeFilterCount = [status, frequency, department, assigneeId].filter( - Boolean, - ).length; - - const filterCategories = getFilterCategories({ - status, - setStatus, - frequency, - setFrequency, - department, - setDepartment, - assigneeId, - setAssigneeId, - frequencies, - departments, - assignees, - setPage, - }); - - // Filter columns for mobile view - const visibleColumns = useMemo(() => { - if (isMobile) { - // On mobile, only show the name and relevance columns - return EvidenceListColumns.filter((column) => column.id === "name"); - } - return EvidenceListColumns; - }, [isMobile]); - - return ( - handleRowClick(row.id)} - emptyMessage="No evidence tasks found." - isLoading={isLoading || isSearching} - pagination={{ - page: Number(page), - pageSize: Number(pageSize), - totalCount: pagination?.totalCount || 0, - totalPages: pagination?.totalPages || 0, - hasNextPage: pagination?.hasNextPage || false, - hasPreviousPage: pagination?.hasPreviousPage || false, - }} - onPageChange={(page) => setPage(page.toString())} - onPageSizeChange={(pageSize) => setPageSize(pageSize.toString())} - search={{ - value: search || "", - onChange: setSearch, - placeholder: "Search evidence tasks...", - }} - filters={{ - categories: filterCategories, - hasActiveFilters, - onClearFilters: clearFilters, - activeFilterCount, - }} - /> - ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/AssigneeAvatar.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/AssigneeAvatar.tsx deleted file mode 100644 index 61ad74e1ba..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/AssigneeAvatar.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -interface AssigneeAvatarProps { - assignee: { - name: string | null; - image: string | null; - }; -} - -export function AssigneeAvatar({ assignee }: AssigneeAvatarProps) { - if (assignee.image) { - return ( - {assignee.name - ); - } - - return ( -
- {(assignee.name || "?").charAt(0)} -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/filterCategories.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/filterCategories.tsx deleted file mode 100644 index 9e86f8bb7c..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/filterCategories.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client"; - -import { STATUS_FILTERS } from "./filterConfigs"; -import { AssigneeAvatar } from "./AssigneeAvatar"; -import { EVIDENCE_STATUS_HEX_COLORS } from "../../../../(overview)/constants/evidence-status"; -import { cn } from "@comp/ui/cn"; -import { Departments } from "@comp/db/types"; -import { EvidenceStatus } from "@comp/db/types"; -import { Frequency } from "@comp/db/types"; - -interface FilterCategoriesProps { - status: EvidenceStatus | null; - setStatus: (status: EvidenceStatus | null) => void; - frequency: Frequency | null; - setFrequency: (frequency: Frequency | null) => void; - department: Departments | null; - setDepartment: (department: Departments | null) => void; - assigneeId: string | null; - setAssigneeId: (assigneeId: string | null) => void; - frequencies: Frequency[]; - departments: Departments[]; - assignees: Array<{ id: string; name: string | null; image: string | null }>; - setPage: (page: string) => void; -} - -export function getFilterCategories({ - status, - setStatus, - frequency, - setFrequency, - department, - setDepartment, - assigneeId, - setAssigneeId, - frequencies, - departments, - assignees, - setPage, -}: FilterCategoriesProps) { - return [ - { - label: "Filter by Status", - items: STATUS_FILTERS.map((filter) => ({ - ...filter, - checked: status === filter.value, - icon: ( -
- ), - onChange: (checked: boolean) => { - setStatus(checked ? filter.value : null); - setPage("1"); - }, - })), - }, - { - label: "Filter by Frequency", - items: frequencies.map((freq) => ({ - label: freq, - value: freq, - checked: frequency === freq, - onChange: (checked: boolean) => { - setFrequency(checked ? freq : null); - setPage("1"); - }, - })), - }, - { - label: "Filter by Department", - items: departments.map((dept) => ({ - label: dept.replace(/_/g, " ").toUpperCase(), - value: dept, - checked: department === dept, - onChange: (checked: boolean) => { - setDepartment(checked ? dept : null); - setPage("1"); - }, - })), - maxHeight: "150px", - }, - { - label: "Filter by Assignee", - items: assignees.map((assignee) => ({ - label: assignee.name || "Unknown", - value: assignee.id, - checked: assigneeId === assignee.id, - onChange: (checked: boolean) => { - setAssigneeId(checked ? assignee.id : null); - setPage("1"); - }, - icon: , - })), - maxHeight: "150px", - }, - ]; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/filterConfigs.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/filterConfigs.tsx deleted file mode 100644 index d7ee2775c7..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/components/filterConfigs.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { CheckCircle2, XCircle, AlertTriangle, Building } from "lucide-react"; - -export const STATUS_FILTERS = [ - { - label: "Published", - value: "published", - icon: , - }, - { - label: "Draft", - value: "draft", - icon: , - }, - { - label: "Not Relevant", - value: "not_relevant", - icon: , - }, -] as const; - -export const DEPARTMENT_ICON = ( - -); diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/index.ts deleted file mode 100644 index 9c70bac4f3..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/components/table/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { EvidenceListTable } from "./EvidenceListTable"; -export { EvidenceListColumns } from "./EvidenceListColumns"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/data/queries.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/data/queries.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/data/queries.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/data/queries.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/data/validations.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/data/validations.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/(overview)/data/validations.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/data/validations.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/hooks/useEvidenceTableContext.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/hooks/useEvidenceTableContext.tsx deleted file mode 100644 index 6c9f8bbd2a..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/hooks/useEvidenceTableContext.tsx +++ /dev/null @@ -1,281 +0,0 @@ -"use client"; - -import { - createContext, - useContext, - useMemo, - useState, - useRef, - useEffect, - type ReactNode, -} from "react"; -import { useQueryState } from "nuqs"; -import type { Frequency, Departments, EvidenceStatus } from "@comp/db/types"; -import { useOrganizationEvidenceTasks } from "../../hooks/useEvidenceTasks"; -import { ALL_DEPARTMENTS } from "../../constants"; -import { ALL_FREQUENCIES } from "../../constants"; -import type { EvidenceTaskRow } from "../types"; - -interface Assignee { - id: string; - name: string | null; - image: string | null; -} - -interface Filter { - label: string; - value: string; - checked: boolean; -} - -interface EvidenceTableContextType { - // State - search: string; - status: EvidenceStatus | null; - frequency: Frequency | null; - department: Departments | null; - assigneeId: string | null; - page: string; - pageSize: string; - filters: Filter[]; - - // Setters - setSearch: (value: string) => void; - setStatus: (value: EvidenceStatus | null) => void; - setFrequency: (value: Frequency | null) => void; - setDepartment: (value: Departments | null) => void; - setAssigneeId: (value: string | null) => void; - setPage: (value: string) => void; - setPageSize: (value: string) => void; - setFilters: (filters: Filter[]) => void; - mutate: () => void; - - // Data - evidenceTasks: EvidenceTaskRow[] | undefined; - pagination: - | { - totalCount: number; - totalPages: number; - hasNextPage: boolean; - hasPreviousPage: boolean; - } - | undefined; - isLoading: boolean; - isSearching: boolean; - error: Error | undefined; - - // Derived data - frequencies: Frequency[]; - departments: Departments[]; - assignees: Assignee[]; - hasActiveFilters: boolean; - - // Actions - clearFilters: () => void; -} - -const EvidenceTableContext = createContext< - EvidenceTableContextType | undefined ->(undefined); - -export function EvidenceTableProvider({ children }: { children: ReactNode }) { - // Local state for search - const [search, setSearch] = useState(""); - - // Query state for other filters - const [statusQuery, setStatusQuery] = useQueryState("status"); - const [frequencyQuery, setFrequencyQuery] = useQueryState("frequency"); - const [departmentQuery, setDepartmentQuery] = useQueryState("department"); - const [assigneeId, setAssigneeId] = useQueryState("assigneeId"); - const [page, setPage] = useQueryState("page", { defaultValue: "1" }); - const [pageSize, setPageSize] = useQueryState("pageSize", { - defaultValue: "10", - }); - - // Type-safe status, frequency, and department - const status = statusQuery as EvidenceStatus | null; - const frequency = frequencyQuery as Frequency | null; - const department = departmentQuery as Departments | null; - - // Type-safe setters - const setStatus = (value: EvidenceStatus | null) => setStatusQuery(value); - const setFrequency = (value: Frequency | null) => setFrequencyQuery(value); - const setDepartment = (value: Departments | null) => - setDepartmentQuery(value); - - // Filter state - const [filters, setFilters] = useState([ - { label: "Published", value: "published", checked: false }, - { label: "Draft", value: "draft", checked: false }, - { label: "Not Relevant", value: "not_relevant", checked: false }, - ]); - - // Track if this is initial load or a search/filter update - const initialLoadCompleted = useRef(false); - const [isSearching, setIsSearching] = useState(false); - - const currentPage = Number.parseInt(page, 10); - const currentPageSize = Number.parseInt(pageSize, 10); - - // Fetch data - const { - data: rawEvidenceTasks, - pagination, - isLoading, - error, - mutate, - } = useOrganizationEvidenceTasks({ - search, - status, - frequency, - department, - assigneeId, - page: currentPage, - pageSize: currentPageSize, - }); - - // Track when search params change - useEffect(() => { - if (initialLoadCompleted.current) { - setIsSearching(true); - } - }, [search, status, frequency, department, assigneeId, page, pageSize]); - - // Track when loading changes - useEffect(() => { - if (isLoading === false) { - // Small delay to ensure UI transitions properly - setTimeout(() => { - initialLoadCompleted.current = true; - setIsSearching(false); - }, 50); - } - }, [isLoading]); - - // Additional safety reset for isSearching when data changes - useEffect(() => { - if (rawEvidenceTasks && isSearching) { - // If we have data, ensure isSearching is eventually set to false - const timer = setTimeout(() => { - setIsSearching(false); - }, 100); - return () => clearTimeout(timer); - } - }, [rawEvidenceTasks, isSearching]); - - // Format data for the table - const evidenceTasks = useMemo(() => { - return rawEvidenceTasks?.map((task) => ({ - ...task, - evidence: { - name: task.name, - }, - })) as EvidenceTaskRow[] | undefined; - }, [rawEvidenceTasks]); - - // Predefined frequencies and departments - const frequencies = useMemo(() => ALL_FREQUENCIES, []); - const departments = useMemo(() => ALL_DEPARTMENTS, []); - - // Get unique assignees for the dropdown - const assignees = useMemo(() => { - if (!evidenceTasks) return []; - - const uniqueAssignees = new Map< - string, - { id: string; name: string | null; image: string | null } - >(); - - for (const task of evidenceTasks) { - if (task.assignee) { - uniqueAssignees.set(task.assignee.id, { - id: task.assignee.id, - name: task.assignee.user.name, - image: task.assignee.user.image, - }); - } - } - - return Array.from(uniqueAssignees.values()).sort((a, b) => { - return (a.name || "").localeCompare(b.name || ""); - }); - }, [evidenceTasks]); - - // Check if any filters are active - const hasActiveFilters = useMemo(() => { - return ( - status !== null || - frequency !== null || - department !== null || - assigneeId !== null - ); - }, [status, frequency, department, assigneeId]); - - // Clear all filters - const clearFilters = () => { - setStatus(null); - setFrequency(null); - setDepartment(null); - setAssigneeId(null); - setPage("1"); // Reset to first page when clearing filters - setFilters(filters.map((f) => ({ ...f, checked: false }))); // Reset all filter checkboxes - setSearch(""); // Clear search - }; - - const contextValue: EvidenceTableContextType = { - // State - search, - status, - frequency, - department, - assigneeId, - page, - pageSize, - filters, - - // Setters - setSearch, - setStatus, - setFrequency, - setDepartment, - setAssigneeId, - setPage, - setPageSize, - setFilters, - mutate, - - // Data - evidenceTasks, - pagination, - isLoading, - isSearching, - error, - - // Derived data - frequencies, - departments, - assignees, - hasActiveFilters, - - // Actions - clearFilters, - }; - - return ( - - {children} - - ); -} - -export function useEvidenceTable() { - const context = useContext(EvidenceTableContext); - - if (context === undefined) { - throw new Error( - "useEvidenceTable must be used within an EvidenceTableProvider", - ); - } - - return context; -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/loading.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/loading.tsx index 9fb4571938..abe4f9f4b4 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/loading.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/loading.tsx @@ -1,16 +1,21 @@ -import { DataTableSkeleton } from "@/components/ui/data-table"; -import { Card } from "@comp/ui/card"; +import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton"; -export default function EvidenceListLoading() { - return ( - - - - ); +export default function Loading() { + return ( + + ); } + diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/page.tsx index e286e0b5d6..1e2575f6c2 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/page.tsx @@ -1,12 +1,53 @@ -import { EvidenceList } from "./components/EvidenceList"; -import { EvidenceTableProvider } from "./hooks/useEvidenceTableContext"; -import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb"; -export default function EvidencePage() { - return ( - - - - - - ); +import { getI18n } from "@/locales/server"; +import type { Metadata } from "next"; +import { setStaticParamsLocale } from "next-international/server"; +import type { SearchParams } from "@/types"; +import { searchParamsCache } from "./data/validations"; +import { getValidFilters } from "@/lib/data-table"; +import { getEvidence } from "./data/queries"; +import { EvidenceTable } from "./components/evidence-table"; + +interface PolicyTableProps { + params: Promise<{ locale: string }>; + searchParams: Promise; } + +export default async function PoliciesPage({ + params, + ...props +}: PolicyTableProps) { + const { locale } = await params; + const searchParams = await props.searchParams; + const search = searchParamsCache.parse(searchParams); + const validFilters = getValidFilters(search.filters); + + setStaticParamsLocale(locale); + + const promises = Promise.all([ + getEvidence({ + ...search, + filters: validFilters, + }), + ]); + + return ( + <> + + + ) +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }>; +}): Promise { + const { locale } = await params; + + setStaticParamsLocale(locale); + const t = await getI18n(); + + return { + title: t("sidebar.evidence"), + }; +} \ No newline at end of file diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/types.ts deleted file mode 100644 index 070ee45619..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/all/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Evidence } from "@comp/db/types"; - -export type EvidenceTaskRow = Evidence & { - assignee?: { - id: string; - user: { - name: string | null; - email: string | null; - image: string | null; - }; - } | null; -}; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/constants.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/constants.ts deleted file mode 100644 index 954658e719..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/evidence/constants.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Departments, Frequency } from "@comp/db/types"; - -// Define all available departments from the enum -export const ALL_DEPARTMENTS: Departments[] = [ - "admin", - "gov", - "hr", - "it", - "itsm", - "qms", -]; - -// Define all available frequencies from the enum -export const ALL_FREQUENCIES: Frequency[] = ["monthly", "quarterly", "yearly"];