From 84693b3da78f113aeef784a74dce9f502228675e Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Thu, 13 Mar 2025 16:09:11 -0400 Subject: [PATCH 1/3] remove view all buttons that did nothing --- .../(dashboard)/(home)/components/FrameworkProgress.tsx | 6 ------ .../(home)/components/RequirementStatusChart.tsx | 3 --- 2 files changed, 9 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx index e806f473bd..5a1047356d 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/FrameworkProgress.tsx @@ -84,12 +84,6 @@ export function FrameworkProgress({ frameworks }: Props) { {t("frameworks.overview.progress.title")} - - View All - {isLoading ? ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/RequirementStatusChart.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/RequirementStatusChart.tsx index a6507b168f..a9e126d529 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/RequirementStatusChart.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/(home)/components/RequirementStatusChart.tsx @@ -141,9 +141,6 @@ export function RequirementStatus({ frameworks }: Props) { {t("frameworks.title")} - - View All - {isLoading ? ( From 6441ef5dafb690fee47a289c50b595c143301318 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Thu, 13 Mar 2025 17:02:52 -0400 Subject: [PATCH 2/3] cleanup evidence/list --- .../EvidenceFilters/ActiveFilterBadges.tsx | 97 -------- .../EvidenceFilters/FilterDropdown.tsx | 207 ------------------ .../EvidenceFilters/PaginationControls.tsx | 78 ------- .../EvidenceFilters/SearchInput.tsx | 42 ---- .../evidence/components/EvidenceList.tsx | 54 ----- .../components/EvidenceSummaryCards.tsx | 111 ---------- .../evidence/components/data-table/index.ts | 3 - .../(dashboard)/evidence/components/index.ts | 3 - .../evidence/list/components/EvidenceList.tsx | 61 ++++++ .../list/components/EvidenceListUIStates.tsx | 110 ++++++++++ .../list/components/EvidenceSummaryCards.tsx | 110 ++++++++++ .../evidence/list/components/index.ts | 3 + .../EvidenceFilters/ActiveFilterBadges.tsx | 97 ++++++++ .../table/EvidenceFilters/FilterDropdown.tsx | 207 ++++++++++++++++++ .../EvidenceFilters/PaginationControls.tsx | 78 +++++++ .../table/EvidenceFilters/SearchInput.tsx | 42 ++++ .../table}/EvidenceFilters/index.ts | 0 .../components/table/EvidenceListColumns.tsx} | 38 ++-- .../components/table/EvidenceListHeader.tsx} | 6 +- .../components/table}/EvidenceListTable.tsx | 43 +++- .../components/table}/SkeletonTable.tsx | 0 .../evidence/list/components/table/index.ts | 3 + .../hooks/useEvidenceTableContext.tsx | 7 +- .../(app)/(dashboard)/evidence/list/page.tsx | 4 +- .../{components/data-table => list}/types.ts | 0 25 files changed, 766 insertions(+), 638 deletions(-) delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/ActiveFilterBadges.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/FilterDropdown.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/PaginationControls.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/SearchInput.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceSummaryCards.tsx delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/index.ts delete mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/index.ts create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceList.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceListUIStates.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceSummaryCards.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/index.ts create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/FilterDropdown.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/PaginationControls.tsx create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/SearchInput.tsx rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{components => list/components/table}/EvidenceFilters/index.ts (100%) rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{components/data-table/columns.tsx => list/components/table/EvidenceListColumns.tsx} (83%) rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{components/data-table/data-table-header.tsx => list/components/table/EvidenceListHeader.tsx} (93%) rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{components/data-table => list/components/table}/EvidenceListTable.tsx (66%) rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{components => list/components/table}/SkeletonTable.tsx (100%) create mode 100644 apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{ => list}/hooks/useEvidenceTableContext.tsx (95%) rename apps/app/src/app/[locale]/(app)/(dashboard)/evidence/{components/data-table => list}/types.ts (100%) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/ActiveFilterBadges.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/ActiveFilterBadges.tsx deleted file mode 100644 index 275ba472c6..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/ActiveFilterBadges.tsx +++ /dev/null @@ -1,97 +0,0 @@ -"use client"; - -import { Badge } from "@bubba/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)/evidence/components/EvidenceFilters/FilterDropdown.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/FilterDropdown.tsx deleted file mode 100644 index 36ae9b690c..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/FilterDropdown.tsx +++ /dev/null @@ -1,207 +0,0 @@ -"use client"; - -import { Button } from "@bubba/ui/button"; -import { Badge } from "@bubba/ui/badge"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, - DropdownMenuCheckboxItem, -} from "@bubba/ui/dropdown-menu"; -import { - CheckCircle2, - Filter, - XCircle, - Building, - AlertTriangle, -} from "lucide-react"; -import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; -import { useEvidenceTable } from "../../hooks/useEvidenceTableContext"; - -export function FilterDropdown() { - const { - status, - setStatus, - frequency, - setFrequency, - department, - setDepartment, - assigneeId, - setAssigneeId, - relevance, - setRelevance, - setPage, - frequencies, - departments, - assignees, - hasActiveFilters, - clearFilters, - } = useEvidenceTable(); - - return ( - - - - - -
- {/* Status, Frequency, and Relevance in first column */} -
- Filter by Status -
- { - setStatus(status === "published" ? null : "published"); - setPage("1"); // Reset to first page when filtering - }} - > -
- - Published -
-
- { - setStatus(status === "draft" ? null : "draft"); - setPage("1"); // Reset to first page when filtering - }} - > -
- - Draft -
-
-
- - Filter by Relevance -
- { - setRelevance(relevance === "relevant" ? null : "relevant"); - setPage("1"); // Reset to first page when filtering - }} - > -
- - Relevant -
-
- { - setRelevance( - relevance === "not-relevant" ? null : "not-relevant", - ); - setPage("1"); // Reset to first page when filtering - }} - > -
- - Not Relevant -
-
-
- - Filter by Frequency -
- {frequencies.map((freq) => ( - { - setFrequency(frequency === freq ? null : freq); - setPage("1"); // Reset to first page when filtering - }} - > - {freq} - - ))} -
-
- - {/* Department and Assignee in second column */} -
- Filter by Department -
- {departments.map((dept) => ( - { - setDepartment(department === dept ? null : dept); - setPage("1"); // Reset to first page when filtering - }} - > -
- - {dept.replace(/_/g, " ").toUpperCase()} -
-
- ))} -
- - Filter by Assignee -
- {assignees.map((assignee) => ( - { - setAssigneeId( - assigneeId === assignee.id ? null : assignee.id, - ); - setPage("1"); // Reset to first page when filtering - }} - > -
- - - - {assignee.name ? assignee.name.charAt(0) : "?"} - - - {assignee.name} -
-
- ))} -
-
-
- - {hasActiveFilters && ( -
- - -
- )} -
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/PaginationControls.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/PaginationControls.tsx deleted file mode 100644 index 44b6aae7b9..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/PaginationControls.tsx +++ /dev/null @@ -1,78 +0,0 @@ -"use client"; - -import { Button } from "@bubba/ui/button"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@bubba/ui/select"; -import { ChevronLeft, ChevronRight } from "lucide-react"; -import { useEvidenceTable } from "../../hooks/useEvidenceTableContext"; - -export function PaginationControls() { - const { page, setPage, pageSize, setPageSize, pagination } = - useEvidenceTable(); - - if (!pagination) return null; - - const { totalPages, totalCount } = pagination; - const currentPage = Number.parseInt(page, 10); - - const handlePageSizeChange = (value: string) => { - setPageSize(value); - setPage("1"); // Reset to first page when changing page size - }; - - return ( -
-
- {totalCount} {totalCount === 1 ? "item" : "items"} -
-
-
-

Rows per page

- -
-
- -
- {currentPage} - - of {totalPages || 1} - -
- -
-
-
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/SearchInput.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/SearchInput.tsx deleted file mode 100644 index 3ca7c05d97..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/SearchInput.tsx +++ /dev/null @@ -1,42 +0,0 @@ -"use client"; - -import { Input } from "@bubba/ui/input"; -import { Search } from "lucide-react"; -import { useEffect, useState } from "react"; -import { useDebounce } from "use-debounce"; -import { useEvidenceTable } from "../../hooks/useEvidenceTableContext"; - -interface SearchInputProps { - placeholder?: string; -} - -export function SearchInput({ - placeholder = "Search evidence...", -}: SearchInputProps) { - const { search, setSearch, setPage } = useEvidenceTable(); - const [inputValue, setInputValue] = useState(search || ""); - const [debouncedValue] = useDebounce(inputValue, 500); - - // Update search query parameter when debounced value changes - useEffect(() => { - if (debouncedValue === "") { - setSearch(null); - } else { - setSearch(debouncedValue); - } - setPage("1"); // Reset to first page when searching - }, [debouncedValue, setSearch, setPage]); - - return ( -
- - setInputValue(e.target.value)} - /> -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx deleted file mode 100644 index 8da64000c7..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import { useI18n } from "@/locales/client"; -import { useEvidenceTable } from "../hooks/useEvidenceTableContext"; -import { DataTable } from "./data-table/EvidenceListTable"; -import { - ActiveFilterBadges, - FilterDropdown, - PaginationControls, - SearchInput, -} from "./EvidenceFilters"; -import { SkeletonTable } from "./SkeletonTable"; -import { EvidenceSummaryCards } from "./EvidenceSummaryCards"; - -export function EvidenceList() { - const t = useI18n(); - const { evidenceTasks = [], isLoading, error } = useEvidenceTable(); - - if (error) return
Error: {error.message}
; - - return ( -
-
- - -
-
- -
- - - - -
-
- - {isLoading ? ( - - ) : ( - <> - {evidenceTasks.length === 0 ? ( -
- No evidence tasks found. Try adjusting your filters. -
- ) : ( - - )} - - - - )} -
- ); -} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceSummaryCards.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceSummaryCards.tsx deleted file mode 100644 index f21df60094..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceSummaryCards.tsx +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; - -import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card"; -import { useEvidenceTable } from "../hooks/useEvidenceTableContext"; -import { FileX, FileCheck, Clock, CheckCircle } from "lucide-react"; -import { useEvidenceTasksStats } from "../hooks/useEvidenceTasksStats"; -import { Skeleton } from "@bubba/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)/evidence/components/data-table/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/index.ts deleted file mode 100644 index 5764381d7d..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { DataTable } from "./EvidenceListTable"; -export { columns } from "./columns"; -export type { EvidenceTaskRow } from "./types"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/index.ts deleted file mode 100644 index 6b6cb79d3d..0000000000 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { EvidenceList } from "./EvidenceList"; -export { SkeletonTable } from "./SkeletonTable"; -export * from "./EvidenceFilters"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceList.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceList.tsx new file mode 100644 index 0000000000..c28dc23580 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceList.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { useEvidenceTasksStats } from "../../hooks/useEvidenceTasksStats"; +import { useEvidenceTable } from "../hooks/useEvidenceTableContext"; +import { + EvidenceListEmpty, + EvidenceListError, + EvidenceListSkeleton, +} from "./EvidenceListUIStates"; +import { EvidenceSummaryCards } from "./EvidenceSummaryCards"; +import { FilterDropdown, SearchInput } from "./table/EvidenceFilters"; +import { PaginationControls } from "./table/EvidenceFilters/PaginationControls"; +import { EvidenceListTable } from "./table/EvidenceListTable"; + +export function EvidenceList() { + const { + evidenceTasks, + isLoading, + error, + mutate, + hasActiveFilters, + clearFilters, + pagination, + } = useEvidenceTable(); + + // Also track the loading state of the stats + const { isLoading: isStatsLoading } = useEvidenceTasksStats(); + + // Show loading state if either the list or stats are loading + if (isLoading || isStatsLoading) { + return ; + } + + if (error) { + return ; + } + + return ( +
+ {/* Evidence summary statistics cards */} + + +
+ + +
+ + {evidenceTasks && evidenceTasks.length === 0 ? ( + + ) : ( +
+ + {pagination && } +
+ )} +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceListUIStates.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceListUIStates.tsx new file mode 100644 index 0000000000..f4e726a430 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceListUIStates.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { SkeletonTable } from "./table/SkeletonTable"; +import { Alert, AlertDescription, AlertTitle } from "@bubba/ui/alert"; +import { AlertCircle, FileQuestion, SearchX } from "lucide-react"; +import { useI18n } from "@/locales/client"; +import { Button } from "@bubba/ui/button"; +import { Skeleton } from "@bubba/ui/skeleton"; + +/** + * Loading state component for the evidence list + */ +export function EvidenceListSkeleton() { + return ( +
+
+ {/* Summary cards skeleton - matching the style in EvidenceSummaryCards */} +
+ {["empty", "draft", "review", "uptodate"].map((type) => ( + + ))} +
+ + {/* Filter controls skeleton */} +
+
+
+
+
+ + {/* Table skeleton */} + +
+ ); +} + +interface ErrorStateProps { + error: Error; + onRetry?: () => 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 && ( + + )} +
+
+ ); +} + +interface EmptyStateProps { + message?: string; + hasFilters?: boolean; + onClearFilters?: () => void; +} + +/** + * 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)/evidence/list/components/EvidenceSummaryCards.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceSummaryCards.tsx new file mode 100644 index 0000000000..d641be0de7 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/EvidenceSummaryCards.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card"; +import { FileX, FileCheck, Clock, CheckCircle } from "lucide-react"; +import { useEvidenceTasksStats } from "../../hooks/useEvidenceTasksStats"; +import { Skeleton } from "@bubba/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)/evidence/list/components/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/index.ts new file mode 100644 index 0000000000..0a68e0ff04 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/index.ts @@ -0,0 +1,3 @@ +export { EvidenceList } from "./EvidenceList"; +export { SkeletonTable } from "./table/SkeletonTable"; +export * from "./table/EvidenceFilters"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx new file mode 100644 index 0000000000..281d596f35 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/ActiveFilterBadges.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { Badge } from "@bubba/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)/evidence/list/components/table/EvidenceFilters/FilterDropdown.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/FilterDropdown.tsx new file mode 100644 index 0000000000..9eb417a4e7 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/FilterDropdown.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { Button } from "@bubba/ui/button"; +import { Badge } from "@bubba/ui/badge"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, + DropdownMenuCheckboxItem, +} from "@bubba/ui/dropdown-menu"; +import { + CheckCircle2, + Filter, + XCircle, + Building, + AlertTriangle, +} from "lucide-react"; +import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; +import { useEvidenceTable } from "../../../hooks/useEvidenceTableContext"; + +export function FilterDropdown() { + const { + status, + setStatus, + frequency, + setFrequency, + department, + setDepartment, + assigneeId, + setAssigneeId, + relevance, + setRelevance, + setPage, + frequencies, + departments, + assignees, + hasActiveFilters, + clearFilters, + } = useEvidenceTable(); + + return ( + + + + + +
+ {/* Status, Frequency, and Relevance in first column */} +
+ Filter by Status +
+ { + setStatus(status === "published" ? null : "published"); + setPage("1"); // Reset to first page when filtering + }} + > +
+ + Published +
+
+ { + setStatus(status === "draft" ? null : "draft"); + setPage("1"); // Reset to first page when filtering + }} + > +
+ + Draft +
+
+
+ + Filter by Relevance +
+ { + setRelevance(relevance === "relevant" ? null : "relevant"); + setPage("1"); // Reset to first page when filtering + }} + > +
+ + Relevant +
+
+ { + setRelevance( + relevance === "not-relevant" ? null : "not-relevant", + ); + setPage("1"); // Reset to first page when filtering + }} + > +
+ + Not Relevant +
+
+
+ + Filter by Frequency +
+ {frequencies.map((freq) => ( + { + setFrequency(frequency === freq ? null : freq); + setPage("1"); // Reset to first page when filtering + }} + > + {freq} + + ))} +
+
+ + {/* Department and Assignee in second column */} +
+ Filter by Department +
+ {departments.map((dept) => ( + { + setDepartment(department === dept ? null : dept); + setPage("1"); // Reset to first page when filtering + }} + > +
+ + {dept.replace(/_/g, " ").toUpperCase()} +
+
+ ))} +
+ + Filter by Assignee +
+ {assignees.map((assignee) => ( + { + setAssigneeId( + assigneeId === assignee.id ? null : assignee.id, + ); + setPage("1"); // Reset to first page when filtering + }} + > +
+ + + + {assignee.name ? assignee.name.charAt(0) : "?"} + + + {assignee.name} +
+
+ ))} +
+
+
+ + {hasActiveFilters && ( +
+ + +
+ )} +
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/PaginationControls.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/PaginationControls.tsx new file mode 100644 index 0000000000..cc89c6a73f --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/PaginationControls.tsx @@ -0,0 +1,78 @@ +"use client"; + +import { Button } from "@bubba/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@bubba/ui/select"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { useEvidenceTable } from "../../../hooks/useEvidenceTableContext"; + +export function PaginationControls() { + const { page, setPage, pageSize, setPageSize, pagination } = + useEvidenceTable(); + + if (!pagination) return null; + + const { totalPages, totalCount } = pagination; + const currentPage = Number.parseInt(page, 10); + + const handlePageSizeChange = (value: string) => { + setPageSize(value); + setPage("1"); // Reset to first page when changing page size + }; + + return ( +
+
+ {totalCount} {totalCount === 1 ? "item" : "items"} +
+
+
+

Rows per page

+ +
+
+ +
+ {currentPage} + + of {totalPages || 1} + +
+ +
+
+
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/SearchInput.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/SearchInput.tsx new file mode 100644 index 0000000000..4edcefed76 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/SearchInput.tsx @@ -0,0 +1,42 @@ +"use client"; + +import { Input } from "@bubba/ui/input"; +import { Search } from "lucide-react"; +import { useEffect, useState } from "react"; +import { useDebounce } from "use-debounce"; +import { useEvidenceTable } from "../../../hooks/useEvidenceTableContext"; + +interface SearchInputProps { + placeholder?: string; +} + +export function SearchInput({ + placeholder = "Search evidence...", +}: SearchInputProps) { + const { search, setSearch, setPage } = useEvidenceTable(); + const [inputValue, setInputValue] = useState(search || ""); + const [debouncedValue] = useDebounce(inputValue, 500); + + // Update search query parameter when debounced value changes + useEffect(() => { + if (debouncedValue === "") { + setSearch(null); + } else { + setSearch(debouncedValue); + } + setPage("1"); // Reset to first page when searching + }, [debouncedValue, setSearch, setPage]); + + return ( +
+ + setInputValue(e.target.value)} + /> +
+ ); +} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/index.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceFilters/index.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceFilters/index.ts diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/columns.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListColumns.tsx similarity index 83% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/columns.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListColumns.tsx index 5e2fdddd2a..c6c0097899 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/columns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListColumns.tsx @@ -1,22 +1,14 @@ "use client"; -import type { ColumnDef } from "@tanstack/react-table"; -import { CheckCircle2, XCircle, Building, AlertTriangle } from "lucide-react"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@bubba/ui/tooltip"; -import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; -import type { EvidenceTaskRow } from "./types"; +import { StatusPolicies } from "@/components/status-policies"; import { calculateNextReview } from "@/lib/utils/calculate-next-review"; +import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; +import type { ColumnDef } from "@tanstack/react-table"; import { format } from "date-fns"; -import { StatusPolicies, type StatusType } from "@/components/status-policies"; -import Link from "next/link"; -import { Button } from "@bubba/ui/button"; +import { AlertTriangle, Building, CheckCircle2 } from "lucide-react"; +import type { EvidenceTaskRow } from "../../types"; -export const columns: ColumnDef[] = [ +export const EvidenceListColumns: ColumnDef[] = [ { id: "name", accessorKey: "name", @@ -27,11 +19,7 @@ export const columns: ColumnDef[] = [ minSize: 200, cell: ({ row }) => (
- + {row.original.name}
[] = [ accessorKey: "published", header: "Status", enableResizing: true, - enableSorting: true, + enableSorting: false, size: 150, minSize: 120, cell: ({ row }) => { @@ -65,7 +53,7 @@ export const columns: ColumnDef[] = [ size: 150, enableResizing: true, minSize: 130, - enableSorting: true, + enableSorting: false, cell: ({ row }) => { const department = row.original.department; if (!department || department === "none") @@ -92,7 +80,7 @@ export const columns: ColumnDef[] = [ size: 150, enableResizing: true, minSize: 130, - enableSorting: true, + enableSorting: false, cell: ({ row }) => { const frequency = row.original.frequency; if (!frequency) return null; @@ -107,7 +95,7 @@ export const columns: ColumnDef[] = [ size: 150, enableResizing: true, minSize: 180, - enableSorting: true, + enableSorting: false, cell: ({ row }) => { if (row.original.lastPublishedAt === null) { return ( @@ -139,7 +127,7 @@ export const columns: ColumnDef[] = [ accessorKey: "assignee", header: "Assignee", enableResizing: true, - enableSorting: true, + enableSorting: false, size: 150, minSize: 150, cell: ({ row }) => { @@ -174,7 +162,7 @@ export const columns: ColumnDef[] = [ accessorKey: "isNotRelevant", header: "Relevance", enableResizing: true, - enableSorting: true, + enableSorting: false, size: 150, minSize: 120, cell: ({ row }) => { diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/data-table-header.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListHeader.tsx similarity index 93% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/data-table-header.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListHeader.tsx index f4f1d0852e..8c88404a94 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/data-table-header.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListHeader.tsx @@ -3,15 +3,15 @@ import type { Table } from "@tanstack/react-table"; import { TableHead, TableHeader, TableRow } from "@bubba/ui/table"; import { flexRender } from "@tanstack/react-table"; -import type { EvidenceTaskRow } from "./types"; +import type { EvidenceTaskRow } from "../../types"; import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react"; import { cn } from "@bubba/ui/cn"; -interface DataTableHeaderProps { +interface EvidenceListHeaderProps { table: Table; } -export function DataTableHeader({ table }: DataTableHeaderProps) { +export function EvidenceListHeader({ table }: EvidenceListHeaderProps) { return ( {table.getHeaderGroups().map((headerGroup) => ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/EvidenceListTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListTable.tsx similarity index 66% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/EvidenceListTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListTable.tsx index 54d89a145b..b495a5038e 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/EvidenceListTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/EvidenceListTable.tsx @@ -9,13 +9,15 @@ import { type SortingState, } from "@tanstack/react-table"; import { Table, TableBody, TableCell, TableRow } from "@bubba/ui/table"; -import { columns } from "./columns"; -import { DataTableHeader } from "./data-table-header"; -import type { EvidenceTaskRow } from "./types"; +import { EvidenceListColumns } from "./EvidenceListColumns"; +import { EvidenceListHeader } from "./EvidenceListHeader"; +import type { EvidenceTaskRow } from "../../types"; import { cn } from "@bubba/ui/cn"; import { useState } from "react"; +import { useRouter } from "next/navigation"; -export function DataTable({ data }: { data: EvidenceTaskRow[] }) { +export function EvidenceListTable({ data }: { data: EvidenceTaskRow[] }) { + const router = useRouter(); const [sorting, setSorting] = useState([ { id: "name", @@ -25,29 +27,45 @@ export function DataTable({ data }: { data: EvidenceTaskRow[] }) { const table = useReactTable({ data, - columns, + columns: EvidenceListColumns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), enableColumnResizing: true, columnResizeMode: "onChange", + defaultColumn: { + minSize: 40, + size: 150, + }, state: { sorting, + columnSizing: { + // Make the relevance column smaller + relevance: 100, + }, }, onSortingChange: setSorting, }); + const handleRowClick = (evidenceId: string) => { + router.push(`/evidence/${evidenceId}`); + }; + return ( -
-
+
+
- + {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( handleRowClick(row.original.id)} > {row.getVisibleCells().map((cell) => ( @@ -77,6 +96,10 @@ export function DataTable({ data }: { data: EvidenceTaskRow[] }) { ? "bg-primary opacity-100" : "" }`} + onClick={(e) => { + // Stop propagation to prevent row click when resizing + e.stopPropagation(); + }} /> ))} @@ -85,7 +108,7 @@ export function DataTable({ data }: { data: EvidenceTaskRow[] }) { ) : ( No evidence tasks found. diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/SkeletonTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/SkeletonTable.tsx similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/SkeletonTable.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/SkeletonTable.tsx diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts new file mode 100644 index 0000000000..8e68bffc60 --- /dev/null +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts @@ -0,0 +1,3 @@ +export { DataTable } from "./EvidenceListTable"; +export { columns } from "./EvidenceListColumns"; +export type { EvidenceTaskRow } from "../../types"; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTableContext.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/hooks/useEvidenceTableContext.tsx similarity index 95% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTableContext.tsx rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/hooks/useEvidenceTableContext.tsx index 1243b9e8a4..d5b0481ff3 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/hooks/useEvidenceTableContext.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/hooks/useEvidenceTableContext.tsx @@ -2,10 +2,11 @@ import { createContext, useContext, useMemo, type ReactNode } from "react"; import { useQueryState } from "nuqs"; -import { useOrganizationEvidenceTasks } from "./useEvidenceTasks"; -import type { EvidenceTaskRow } from "../components/data-table/types"; -import { ALL_DEPARTMENTS, ALL_FREQUENCIES } from "../constants"; import type { Frequency, Departments } from "@bubba/db"; +import { useOrganizationEvidenceTasks } from "../../hooks/useEvidenceTasks"; +import { ALL_DEPARTMENTS } from "../../constants"; +import { ALL_FREQUENCIES } from "../../constants"; +import type { EvidenceTaskRow } from "../components/table"; interface Assignee { id: string; diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/page.tsx index 3c25a83bd7..a65d876666 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/page.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/page.tsx @@ -1,5 +1,5 @@ -import { EvidenceList } from "../components/EvidenceList"; -import { EvidenceTableProvider } from "../hooks/useEvidenceTableContext"; +import { EvidenceList } from "./components/EvidenceList"; +import { EvidenceTableProvider } from "./hooks/useEvidenceTableContext"; export default function EvidencePage() { return ( diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/types.ts similarity index 100% rename from apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/types.ts rename to apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/types.ts From 8ea3473a5942b01fb2b336e2872655eb592248a4 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Thu, 13 Mar 2025 17:57:43 -0400 Subject: [PATCH 3/3] fix exports --- .../(app)/(dashboard)/evidence/list/components/table/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts index 8e68bffc60..0e9aae755b 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/list/components/table/index.ts @@ -1,3 +1,3 @@ -export { DataTable } from "./EvidenceListTable"; -export { columns } from "./EvidenceListColumns"; +export { EvidenceListTable } from "./EvidenceListTable"; +export { EvidenceListColumns } from "./EvidenceListColumns"; export type { EvidenceTaskRow } from "../../types";