From d7fa8b315e8b6412fc75be16374f4fb7f2a7e98a Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 13:39:56 -0400 Subject: [PATCH 1/9] make evidence details mobile responsive --- .../[id]/components/ReviewSection.tsx | 278 +++++++++--------- 1 file changed, 139 insertions(+), 139 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewSection.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewSection.tsx index 83ef3a4a79..11a77d24b5 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewSection.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/ReviewSection.tsx @@ -16,152 +16,152 @@ import { DepartmentSection } from "./DepartmentSection"; import { FrequencySection } from "./FrequencySection"; interface ReviewSectionProps { - evidence: OrganizationEvidence; - evidenceId: string; - lastPublishedAt: Date | null; - frequency: Frequency | null; - department: string | null; - currentAssigneeId: string | null | undefined; - onSuccess: () => Promise; - id: string; + evidence: OrganizationEvidence; + evidenceId: string; + lastPublishedAt: Date | null; + frequency: Frequency | null; + department: string | null; + currentAssigneeId: string | null | undefined; + onSuccess: () => Promise; + id: string; } export function ReviewSection({ - evidenceId, - lastPublishedAt, - frequency, - department, - currentAssigneeId, - onSuccess, - id, - evidence, + evidenceId, + lastPublishedAt, + frequency, + department, + currentAssigneeId, + onSuccess, + id, + evidence, }: ReviewSectionProps) { - const { mutate } = useOrganizationEvidence({ id }); - const reviewInfo = calculateNextReview(lastPublishedAt, frequency); + const { mutate } = useOrganizationEvidence({ id }); + const reviewInfo = calculateNextReview(lastPublishedAt, frequency); - const { execute: toggleRelevanceAction, isExecuting: isTogglingRelevance } = - useAction(toggleRelevance, { - onSuccess: () => { - toast.success("Evidence relevance updated successfully"); - mutate(); - }, - onError: () => { - toast.error("Failed to update evidence relevance, please try again."); - }, - }); + const { execute: toggleRelevanceAction, isExecuting: isTogglingRelevance } = + useAction(toggleRelevance, { + onSuccess: () => { + toast.success("Evidence relevance updated successfully"); + mutate(); + }, + onError: () => { + toast.error("Failed to update evidence relevance, please try again."); + }, + }); - const { execute: publishAction, isExecuting } = useAction(publishEvidence, { - onSuccess: () => { - toast.success("Evidence published successfully"); - mutate(); - }, - onError: () => { - toast.error("Failed to publish evidence, please try again."); - }, - }); + const { execute: publishAction, isExecuting } = useAction(publishEvidence, { + onSuccess: () => { + toast.success("Evidence published successfully"); + mutate(); + }, + onError: () => { + toast.error("Failed to publish evidence, please try again."); + }, + }); - return ( - - - -
-

Evidence Overview

-

- Manage review frequency, department assignment, and track upcoming - review dates -

-
- -
-
- -
-
-
- -

- DEPARTMENT -

-
- -
+ return ( + + + +
+

Evidence Overview

+

+ Manage review frequency, department assignment, and track upcoming + review dates +

+
+ +
+
+ +
+
+
+ +

+ DEPARTMENT +

+
+ +
-
-
- -

- FREQUENCY -

-
- -
+
+
+ +

+ FREQUENCY +

+
+ +
-
-
- -

- NEXT REVIEW -

-
- {!reviewInfo ? ( -

ASAP

- ) : ( -
- {reviewInfo.daysUntil} days ( - {format(reviewInfo.nextReviewDate, "MM/dd/yyyy")}) -
- )} -
-
-
- -

- ASSIGNEE -

-
- -
-
- {!evidence.published && ( - - )} -
-
- ); +
+
+ +

+ NEXT REVIEW +

+
+ {!reviewInfo ? ( +

ASAP

+ ) : ( +
+ {reviewInfo.daysUntil} days ( + {format(reviewInfo.nextReviewDate, "MM/dd/yyyy")}) +
+ )} +
+
+
+ +

+ ASSIGNEE +

+
+ +
+
+ {!evidence.published && ( + + )} +
+
+ ); } From 848acd86ae367b9fc1cdb7b5c8a60633774c5eeb Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 13:40:13 -0400 Subject: [PATCH 2/9] adapt table to be mobile responsive --- .../[id]/components/EvidenceDetails.tsx | 120 +++++++------- .../evidence/components/EvidenceList.tsx | 151 +++++++++++++----- 2 files changed, 169 insertions(+), 102 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/EvidenceDetails.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/EvidenceDetails.tsx index 224321b079..53ac22c59e 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/EvidenceDetails.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/[id]/components/EvidenceDetails.tsx @@ -8,71 +8,71 @@ import type { EvidenceDetailsProps } from "../types"; import { ReviewSection } from "./ReviewSection"; export function EvidenceDetails({ id }: EvidenceDetailsProps) { - const { data, isLoading, error, mutate } = useOrganizationEvidence({ id }); + const { data, isLoading, mutate } = useOrganizationEvidence({ id }); - if (isLoading) { - return ( -
- - - -
- ); - } + if (isLoading) { + return ( +
+ + + +
+ ); + } - if (!data?.data) return null; + if (!data?.data) return null; - const evidence = data.data; + const evidence = data.data; - const handleMutate = async () => { - await mutate(); - }; + const handleMutate = async () => { + await mutate(); + }; - return ( -
- {/* Alert with evidence info and status */} - - - -
- {evidence.evidence.name} Evidence -
- {evidence.published ? ( -
- - Published -
- ) : ( -
- - Draft -
- )} -
-
-
- - {evidence.description || "No description provided."} + return ( +
+ {/* Alert with evidence info and status */} + + + +
+ {evidence.evidence.name} Evidence +
+ {evidence.published ? ( +
+ + Published +
+ ) : ( +
+ + Draft +
+ )} +
+
+
+ + {evidence.description || "No description provided."} - {evidence.isNotRelevant && ( -
- This evidence has been marked as not relevant and will not be - included in compliance reports. -
- )} -
-
+ {evidence.isNotRelevant && ( +
+ This evidence has been marked as not relevant and will not be + included in compliance reports. +
+ )} + + - -
- ); + +
+ ); } 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 index bf7707ab42..961f70acfc 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx @@ -4,51 +4,118 @@ import { useI18n } from "@/locales/client"; import { useEvidenceTable } from "../hooks/useEvidenceTableContext"; import { DataTable } from "./data-table/EvidenceListTable"; import { - ActiveFilterBadges, - FilterDropdown, - PaginationControls, - SearchInput, + ActiveFilterBadges, + FilterDropdown, + PaginationControls, + SearchInput, } from "./EvidenceFilters"; import { SkeletonTable } from "./SkeletonTable"; import { EvidenceSummaryCards } from "./EvidenceSummaryCards"; +import { useRouter } from "next/navigation"; +import { StatusPolicies } from "@/components/status-policies"; +import type { EvidenceTaskRow } from "./data-table/types"; +import { Skeleton } from "@bubba/ui/skeleton"; + +// Mobile skeleton loader +function MobileSkeletonLoader() { + return ( +
+ {[ + "skeleton-item-1", + "skeleton-item-2", + "skeleton-item-3", + "skeleton-item-4", + "skeleton-item-5", + ].map((id) => ( +
+ + +
+ ))} +
+ ); +} + +// Mobile view evidence list item +function MobileEvidenceList({ data }: { data: EvidenceTaskRow[] }) { + const router = useRouter(); + + return ( +
+ {data.map((item) => ( +
router.push(`/evidence/${item.id}`)} + > +
{item.name}
+ +
+ ))} +
+ ); +} 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. -
- ) : ( - - )} - - - - )} -
- ); + 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. +
+ ) : ( + <> + {/* Show table on larger screens, hide on mobile */} +
+ +
+ + {/* Show list on mobile, hide on larger screens */} +
+ +
+ + )} + + + + )} +
+ ); } From 5462898baf1361ee321fb32fa79821ce59d2c220 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 13:40:25 -0400 Subject: [PATCH 3/9] fix skeleton loaders --- .../evidence/components/EvidenceList.tsx | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) 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 index 961f70acfc..d90c55cf17 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx @@ -20,21 +20,17 @@ import { Skeleton } from "@bubba/ui/skeleton"; function MobileSkeletonLoader() { return (
- {[ - "skeleton-item-1", - "skeleton-item-2", - "skeleton-item-3", - "skeleton-item-4", - "skeleton-item-5", - ].map((id) => ( -
- - -
- ))} + {Array(5) + .fill(0) + .map((_, i) => ( +
+ + +
+ ))}
); } From 135e340edf7523dade0f42bc4b1ac3f934a380d5 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 13:52:57 -0400 Subject: [PATCH 4/9] match evidence table mobile with other --- .../evidence/components/EvidenceList.tsx | 67 +--- .../evidence/components/SkeletonTable.tsx | 102 +++--- .../data-table/EvidenceListTable.tsx | 165 ++++----- .../components/data-table/columns.tsx | 335 +++++++++--------- .../data-table/data-table-header.tsx | 107 +++--- 5 files changed, 381 insertions(+), 395 deletions(-) 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 index d90c55cf17..8da64000c7 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/EvidenceList.tsx @@ -11,52 +11,6 @@ import { } from "./EvidenceFilters"; import { SkeletonTable } from "./SkeletonTable"; import { EvidenceSummaryCards } from "./EvidenceSummaryCards"; -import { useRouter } from "next/navigation"; -import { StatusPolicies } from "@/components/status-policies"; -import type { EvidenceTaskRow } from "./data-table/types"; -import { Skeleton } from "@bubba/ui/skeleton"; - -// Mobile skeleton loader -function MobileSkeletonLoader() { - return ( -
- {Array(5) - .fill(0) - .map((_, i) => ( -
- - -
- ))} -
- ); -} - -// Mobile view evidence list item -function MobileEvidenceList({ data }: { data: EvidenceTaskRow[] }) { - const router = useRouter(); - - return ( -
- {data.map((item) => ( -
router.push(`/evidence/${item.id}`)} - > -
{item.name}
- -
- ))} -
- ); -} export function EvidenceList() { const t = useI18n(); @@ -81,14 +35,7 @@ export function EvidenceList() { {isLoading ? ( - <> -
- -
-
- -
- + ) : ( <> {evidenceTasks.length === 0 ? ( @@ -96,17 +43,7 @@ export function EvidenceList() { No evidence tasks found. Try adjusting your filters. ) : ( - <> - {/* Show table on larger screens, hide on mobile */} -
- -
- - {/* Show list on mobile, hide on larger screens */} -
- -
- + )} diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/SkeletonTable.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/SkeletonTable.tsx index 1961ba46b5..6ee50126af 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/SkeletonTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/SkeletonTable.tsx @@ -1,52 +1,68 @@ import { Skeleton } from "@bubba/ui/skeleton"; import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, } from "@bubba/ui/table"; +import { cn } from "@bubba/ui/cn"; const SKELETON_ROWS = [ - "skeleton-1", - "skeleton-2", - "skeleton-3", - "skeleton-4", - "skeleton-5", + "skeleton-1", + "skeleton-2", + "skeleton-3", + "skeleton-4", + "skeleton-5", ] as const; export const SkeletonTable = () => { - return ( - - - - - - - - - - - - - - - - {SKELETON_ROWS.map((key) => ( - - - - - - - - - - - - ))} - -
- ); + return ( + + + + + + + + + + + + + + + + + + + + + + {SKELETON_ROWS.map((key) => ( + + +
+ + +
+
+ + + + + + + + + + + + +
+ ))} +
+
+ ); }; 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/components/data-table/EvidenceListTable.tsx index b72f4f9f94..54d89a145b 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/EvidenceListTable.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/EvidenceListTable.tsx @@ -1,93 +1,100 @@ "use client"; import { - type Column, - flexRender, - getCoreRowModel, - useReactTable, - getSortedRowModel, - type SortingState, + type Column, + flexRender, + getCoreRowModel, + useReactTable, + getSortedRowModel, + 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 { useRouter } from "next/navigation"; +import { cn } from "@bubba/ui/cn"; import { useState } from "react"; export function DataTable({ data }: { data: EvidenceTaskRow[] }) { - const router = useRouter(); - const [sorting, setSorting] = useState([ - { - id: "name", - desc: false, - }, - ]); + const [sorting, setSorting] = useState([ + { + id: "name", + desc: false, + }, + ]); - const table = useReactTable({ - data, - columns, - getCoreRowModel: getCoreRowModel(), - getSortedRowModel: getSortedRowModel(), - enableColumnResizing: true, - columnResizeMode: "onChange", - state: { - sorting, - }, - onSortingChange: setSorting, - }); + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + enableColumnResizing: true, + columnResizeMode: "onChange", + state: { + sorting, + }, + onSortingChange: setSorting, + }); - return ( -
-
- - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - router.push(`/evidence/${row.original.id}`)} - > - {row.getVisibleCells().map((cell) => ( - -
- {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} -
-
- - ))} - - )) - ) : ( - - - No evidence tasks found. - - - )} - -
-
-
- ); + return ( +
+
+ + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + +
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} +
+
+ + ))} + + )) + ) : ( + + + No evidence tasks found. + + + )} + +
+
+
+ ); } 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/components/data-table/columns.tsx index 83a8fe9a01..5e2fdddd2a 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/columns.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/evidence/components/data-table/columns.tsx @@ -3,181 +3,198 @@ import type { ColumnDef } from "@tanstack/react-table"; import { CheckCircle2, XCircle, Building, AlertTriangle } from "lucide-react"; import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, } from "@bubba/ui/tooltip"; import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar"; import type { EvidenceTaskRow } from "./types"; import { calculateNextReview } from "@/lib/utils/calculate-next-review"; import { format } from "date-fns"; import { StatusPolicies, type StatusType } from "@/components/status-policies"; +import Link from "next/link"; +import { Button } from "@bubba/ui/button"; export const columns: ColumnDef[] = [ - { - id: "name", - accessorKey: "name", - header: "Name", - enableResizing: true, - enableSorting: true, - size: 100, - minSize: 200, - cell: ({ row }) => ( - - - -
{row.original.name}
-
- {row.original.description} -
-
- ), - }, - { - id: "status", - accessorKey: "published", - header: "Status", - enableResizing: true, - enableSorting: true, - size: 150, - minSize: 120, - cell: ({ row }) => { - const isPublished = row.original.published; - const label = isPublished ? "Published" : "Draft"; + { + id: "name", + accessorKey: "name", + header: "Name", + enableResizing: true, + enableSorting: true, + size: 100, + minSize: 200, + cell: ({ row }) => ( +
+ +
+ +
+
+ ), + }, + { + id: "status", + accessorKey: "published", + header: "Status", + enableResizing: true, + enableSorting: true, + size: 150, + minSize: 120, + cell: ({ row }) => { + const isPublished = row.original.published; - return ( -
- -
- ); - }, - }, - { - id: "department", - accessorKey: "department", - header: "Department", - size: 150, - enableResizing: true, - minSize: 130, - enableSorting: true, - cell: ({ row }) => { - const department = row.original.department; - if (!department || department === "none") - return
None
; + return ( +
+ +
+ ); + }, + }, + { + id: "department", + accessorKey: "department", + header: "Department", + size: 150, + enableResizing: true, + minSize: 130, + enableSorting: true, + 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: true, - cell: ({ row }) => { - const frequency = row.original.frequency; - if (!frequency) return null; + return ( +
+ + + {department.replace(/_/g, " ").toUpperCase()} + +
+ ); + }, + }, + { + id: "frequency", + accessorKey: "frequency", + header: "Frequency", + size: 150, + enableResizing: true, + minSize: 130, + enableSorting: true, + 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: true, - cell: ({ row }) => { - if (row.original.lastPublishedAt === null) { - return
ASAP
; - } + return
{frequency}
; + }, + }, + { + id: "nextReviewDate", + accessorKey: "nextReviewDate", + header: "Next Review Date", + size: 150, + enableResizing: true, + minSize: 180, + enableSorting: true, + cell: ({ row }) => { + if (row.original.lastPublishedAt === null) { + return ( +
+ ASAP +
+ ); + } - const reviewInfo = calculateNextReview( - row.original.lastPublishedAt, - row.original.frequency, - ); + const reviewInfo = calculateNextReview( + row.original.lastPublishedAt, + row.original.frequency, + ); - if (!reviewInfo) return null; + if (!reviewInfo) return null; - return ( -
- {reviewInfo.daysUntil} days ( - {format(reviewInfo.nextReviewDate, "MM/dd/yyyy")}) -
- ); - }, - }, - { - id: "assignee", - accessorKey: "assignee", - header: "Assignee", - enableResizing: true, - enableSorting: true, - size: 150, - minSize: 150, - cell: ({ row }) => { - const assignee = row.original.assignee; + return ( + + ); + }, + }, + { + id: "assignee", + accessorKey: "assignee", + header: "Assignee", + enableResizing: true, + enableSorting: true, + size: 150, + minSize: 150, + cell: ({ row }) => { + const assignee = row.original.assignee; - if (!assignee) { - return
Unassigned
; - } + if (!assignee) { + return ( +
+ Unassigned +
+ ); + } - return ( -
- - - - {assignee.name ? assignee.name.charAt(0) : "?"} - - - {assignee.name} -
- ); - }, - }, - { - id: "relevance", - accessorKey: "isNotRelevant", - header: "Relevance", - enableResizing: true, - enableSorting: true, - size: 150, - minSize: 120, - cell: ({ row }) => { - const isNotRelevant = row.original.isNotRelevant; + return ( +
+ + + + {assignee.name ? assignee.name.charAt(0) : "?"} + + + {assignee.name} +
+ ); + }, + }, + { + id: "relevance", + accessorKey: "isNotRelevant", + header: "Relevance", + enableResizing: true, + enableSorting: true, + size: 150, + minSize: 120, + cell: ({ row }) => { + const isNotRelevant = row.original.isNotRelevant; - if (!isNotRelevant) { - return ( -
- - Relevant -
- ); - } + if (!isNotRelevant) { + return ( +
+ + Relevant +
+ ); + } - return ( -
- - Not Relevant -
- ); - }, - }, + return ( +
+ + Not Relevant +
+ ); + }, + }, ]; 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/components/data-table/data-table-header.tsx index e02f6cd071..f4f1d0852e 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/components/data-table/data-table-header.tsx @@ -8,56 +8,65 @@ import { ArrowDown, ArrowUp, ArrowUpDown } from "lucide-react"; import { cn } from "@bubba/ui/cn"; interface DataTableHeaderProps { - table: Table; + table: Table; } export function DataTableHeader({ table }: DataTableHeaderProps) { - 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() && ( - - ))} -
- )} -
- - ))} - - ))} - - ); + 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() && ( + + ))} +
+ )} +
+ + ))} + + ))} + + ); } From 88d8f9063a582bff7e46fbe0646bf7385f9423a9 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 14:01:43 -0400 Subject: [PATCH 5/9] Make employees table mobile responsive --- .../components/EmployeesListSkeleton.tsx | 90 ++++++--- .../tables/people/data-table-header.tsx | 23 ++- .../components/tables/people/data-table.tsx | 173 +++++++++--------- 3 files changed, 163 insertions(+), 123 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesListSkeleton.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesListSkeleton.tsx index 46f3e8bb08..bd1d650f8f 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesListSkeleton.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/people/components/EmployeesListSkeleton.tsx @@ -1,32 +1,68 @@ import { FilterToolbar } from "@/components/tables/people/filter-toolbar"; import { Skeleton } from "@bubba/ui/skeleton"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@bubba/ui/table"; + +const SKELETON_ROWS = [ + "skeleton-1", + "skeleton-2", + "skeleton-3", + "skeleton-4", + "skeleton-5", +] as const; export const EmployeesListSkeleton = () => { - return ( -
- -
-
- - - -
-
- - - -
-
- - - -
-
- - - -
-
-
- ); + return ( +
+ +
+ + + + + + + + + + + + + + + + + + + {SKELETON_ROWS.map((key) => ( + + +
+ + + +
+
+ + + + + + + + + +
+ ))} +
+
+
+
+ ); }; diff --git a/apps/app/src/components/tables/people/data-table-header.tsx b/apps/app/src/components/tables/people/data-table-header.tsx index d6d8b11622..41f0b8d345 100644 --- a/apps/app/src/components/tables/people/data-table-header.tsx +++ b/apps/app/src/components/tables/people/data-table-header.tsx @@ -7,6 +7,7 @@ import { TableHead, TableHeader, TableRow } from "@bubba/ui/table"; import { ArrowDown, ArrowUp } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useCallback } from "react"; +import { cn } from "@bubba/ui/cn"; type Props = { table?: { @@ -59,27 +60,29 @@ export function DataTableHeader({ table, loading }: Props) { return ( - {isVisible("email") && ( - + {isVisible("name") && ( + )} - {isVisible("name") && ( + {isVisible("email") && ( )} @@ -103,7 +106,7 @@ export function DataTableHeader({ table, loading }: Props) { )} {isVisible("status") && ( - + - + ), + cell: ({ row }) => { + const name = row.original.name; + const email = row.original.email; + const isActive = row.original.isActive; + const status = getEmployeeStatusFromBoolean(isActive); + + return ( +
+ +
+ {email} +
+
+ +
+
+ ); + }, }, { - id: "name", - accessorKey: "name", + id: "email", + accessorKey: "email", header: ({ column }) => ( - - - + ), + cell: ({ row }) => { + const email = row.original.email; + return ( +
{email}
+ ); + }, }, { id: "department", accessorKey: "department", header: ({ column }) => ( - - - + ), cell: ({ row }) => { const department = row.original.department; return ( -
+
{department}
); @@ -116,7 +124,11 @@ function getColumns(): ColumnDef[] { const isActive = row.original.isActive; const status = getEmployeeStatusFromBoolean(isActive); - return ; + return ( +
+ +
+ ); }, enableSorting: true, enableHiding: true, @@ -130,13 +142,8 @@ export function DataTable({ pageCount, currentPage, }: DataTableProps) { + const columns = getColumns(); const router = useRouter(); - const clientColumns = getColumns(); - const columns = clientColumns.map((col) => ({ - ...col, - header: columnHeaders[col.id as keyof typeof columnHeaders], - accessorFn: (row: PersonType) => row[col.id as keyof PersonType], - })); const table = useReactTable({ data, @@ -147,55 +154,49 @@ export function DataTable({ }); return ( -
- - - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - { - const person = row.original; - router.push(`/people/${person.id}`); - }} - > - {row.getVisibleCells().map((cell) => { - let cellClassName = ""; - - if (cell.column.id === "name") { - cellClassName = "w-[30%]"; - } else if (cell.column.id === "email") { - cellClassName = "w-[30%] hidden md:table-cell"; - } else if (cell.column.id === "department") { - cellClassName = "w-[20%] uppercase"; - } else if (cell.column.id === "status") { - cellClassName = "w-[20%] text-center"; - } - - return ( - +
+
+
+ + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender( cell.column.columnDef.cell, cell.getContext(), )} - ); - })} + ))} + + )) + ) : ( + + + No results. + - )) - ) : ( - - - No results. - - - )} - -
+ )} + + +
); From c615b3166d1c81c2487c869aaaf2354b39495f85 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 14:17:43 -0400 Subject: [PATCH 6/9] Enhance EditableDepartment component for better responsiveness and styling. Added max-width constraints and truncation for department display, ensuring a cleaner layout on mobile devices. --- .../components/EditableDepartment.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EditableDepartment.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EditableDepartment.tsx index 07ac5575b5..6e163f1f1f 100644 --- a/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EditableDepartment.tsx +++ b/apps/app/src/app/[locale]/(app)/(dashboard)/people/[employeeId]/components/EditableDepartment.tsx @@ -15,6 +15,7 @@ import { useAction } from "next-safe-action/hooks"; import { updateEmployeeDepartment } from "../actions/update-department"; import { Pencil, Check, X } from "lucide-react"; import type { Departments } from "@bubba/db"; +import { cn } from "@bubba/ui/cn"; const DEPARTMENTS = [ { value: "admin", label: "Admin" }, @@ -62,7 +63,7 @@ export function EditableDepartment({ if (!isEditing) { return ( -
+
-

+

{DEPARTMENTS.find((d) => d.value === currentDepartment)?.label || currentDepartment}

@@ -83,14 +84,16 @@ export function EditableDepartment({ } return ( -
+
-
+
-
+
+ +
+ + ); + + // Created key content for reuse in both Dialog and Sheet + const renderCreatedKeyContent = () => ( + <> +
+
+

+ {t("settings.api_keys.api_key")} +

+
+
+
+
+ {createdApiKey} +
+
+ +
+
+

+ {t("settings.api_keys.save_warning")} +

+
+
+
+ +
+ + ); + + // Render different UI components for mobile vs desktop + if (isMobile) { + return ( + + +
+ {createdApiKey ? ( + <> + + + {t("settings.api_keys.created_title")} + + + {t("settings.api_keys.created_description")} + + + {renderCreatedKeyContent()} + + ) : ( + <> + + {t("settings.api_keys.create_title")} + + {t("settings.api_keys.create_description")} + + + {renderFormContent()} + + )} +
+
+
+ ); + } + return ( - + {createdApiKey ? ( <> @@ -107,45 +262,7 @@ export function CreateApiKeyDialog({ {t("settings.api_keys.created_description")} -
-
-

- {t("settings.api_keys.api_key")} -

-
-
-
-
- - {createdApiKey} - -
-
- -
-
-

- {t("settings.api_keys.save_warning")} -

-
-
- - - + {renderCreatedKeyContent()} ) : ( <> @@ -155,77 +272,7 @@ export function CreateApiKeyDialog({ {t("settings.api_keys.create_description")} -
-
- - setName(e.target.value)} - placeholder={t("settings.api_keys.name_placeholder")} - required - className="w-full" - /> -
-
- - -
- - - - -
+ {renderFormContent()} )}
From 0a0940f2692aa6c445fe2fd45943ca6c2b876674 Mon Sep 17 00:00:00 2001 From: Mariano Fuentes Date: Wed, 12 Mar 2025 15:01:21 -0400 Subject: [PATCH 9/9] Enhance FrameworkProgress component for improved mobile responsiveness. Implemented media query for adaptive label display, ensuring concise text on smaller screens. Updated layout for better visual alignment and user experience across devices. --- .../(home)/components/FrameworkProgress.tsx | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 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 60f23383e7..e806f473bd 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 @@ -11,6 +11,8 @@ import { Button } from "@bubba/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card"; import { FileStack } from "lucide-react"; import Link from "next/link"; +import { useMediaQuery } from "@bubba/ui/hooks"; +import type { ReactNode } from "react"; interface Props { frameworks: (OrganizationFramework & { @@ -29,13 +31,15 @@ export function FrameworkProgress({ frameworks }: Props) { isLoading, } = useComplianceScores({ frameworks }); + const isMobile = useMediaQuery("(max-width: 640px)"); + const CircleProgress = ({ percentage, label, href, }: { percentage: number; - label: string; + label: ReactNode; href: string; }) => ( {percentage}%
-
- {label} +
+ {label}
); @@ -156,12 +160,22 @@ export function FrameworkProgress({ frameworks }: Props) { /> + Evidence + Evidence Tasks + + } href="/evidence/list" /> + Tests + Cloud Tests + + } href="/tests" />