Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ export const getEvidenceTasksStats = authActionClient

const isPastDue = nextReviewDate && nextReviewDate < now;

if (!hasContent) {
if (isPublished) {
upToDateCount++;
}

if (!hasContent && !isPublished) {
// No files or links
emptyCount++;
} else if (!isPublished) {
Expand All @@ -96,9 +100,6 @@ export const getEvidenceTasksStats = authActionClient
} else if (isPastDue) {
// Published but needs review
needsReviewCount++;
} else {
// Published and up to date
upToDateCount++;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const getOrganizationEvidenceTasks = authActionClient
frequency: z.nativeEnum(Frequency).optional().nullable(),
department: z.nativeEnum(Departments).optional().nullable(),
assigneeId: z.string().optional().nullable(),
relevance: z.enum(["relevant", "not-relevant"]).optional().nullable(),
page: z.number().int().positive().optional().default(1),
pageSize: z.number().int().positive().optional().default(10),
})
Expand All @@ -47,6 +48,7 @@ export const getOrganizationEvidenceTasks = authActionClient
frequency,
department,
assigneeId,
relevance,
page,
pageSize,
} = parsedInput;
Expand All @@ -72,6 +74,9 @@ export const getOrganizationEvidenceTasks = authActionClient
...(department ? { department } : {}),
// Assignee filter
...(assigneeId ? { assigneeId } : {}),
// Relevance filter
...(relevance === "relevant" ? { isNotRelevant: false } : {}),
...(relevance === "not-relevant" ? { isNotRelevant: true } : {}),
// Search filter
...(search
? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export function ActiveFilterBadges() {
setDepartment,
assigneeId,
setAssigneeId,
relevance,
setRelevance,
setPage,
assignees,
} = useEvidenceTable();
Expand All @@ -24,7 +26,7 @@ export function ActiveFilterBadges() {
: null;

// Only render if there are active filters
if (!status && !frequency && !department && !assigneeId) {
if (!status && !frequency && !department && !assigneeId && !relevance) {
return null;
}

Expand All @@ -42,6 +44,18 @@ export function ActiveFilterBadges() {
/>
</Badge>
)}
{relevance && (
<Badge variant="outline" className="flex items-center gap-1">
Relevance: {relevance === "relevant" ? "Relevant" : "Not Relevant"}
<X
className="h-3 w-3 cursor-pointer"
onClick={() => {
setRelevance(null);
setPage("1");
}}
/>
</Badge>
)}
{frequency && (
<Badge variant="outline" className="flex items-center gap-1">
Frequency: {frequency}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
DropdownMenuTrigger,
DropdownMenuCheckboxItem,
} from "@bubba/ui/dropdown-menu";
import { CheckCircle2, Filter, XCircle, Building } from "lucide-react";
import {
CheckCircle2,
Filter,
XCircle,
Building,
AlertTriangle,
} from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@bubba/ui/avatar";
import { useEvidenceTable } from "../../hooks/useEvidenceTableContext";

Expand All @@ -24,6 +30,8 @@ export function FilterDropdown() {
setDepartment,
assigneeId,
setAssigneeId,
relevance,
setRelevance,
setPage,
frequencies,
departments,
Expand All @@ -43,112 +51,155 @@ export function FilterDropdown() {
{(status ? 1 : 0) +
(frequency ? 1 : 0) +
(department ? 1 : 0) +
(assigneeId ? 1 : 0)}
(assigneeId ? 1 : 0) +
(relevance ? 1 : 0)}
</Badge>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>Filter by Status</DropdownMenuLabel>
<DropdownMenuCheckboxItem
checked={status === "published"}
onCheckedChange={() => {
setStatus(status === "published" ? null : "published");
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<CheckCircle2 size={16} className="text-green-500" />
<span>Published</span>
</div>
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={status === "draft"}
onCheckedChange={() => {
setStatus(status === "draft" ? null : "draft");
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<XCircle size={16} className="text-red-500" />
<span>Draft</span>
</div>
</DropdownMenuCheckboxItem>

<DropdownMenuSeparator />

<DropdownMenuLabel>Filter by Frequency</DropdownMenuLabel>
{frequencies.map((freq) => (
<DropdownMenuCheckboxItem
key={freq}
checked={frequency === freq}
onCheckedChange={() => {
setFrequency(frequency === freq ? null : freq);
setPage("1"); // Reset to first page when filtering
}}
>
{freq}
</DropdownMenuCheckboxItem>
))}
<DropdownMenuContent align="end" className="w-[500px] max-w-[90vw]">
<div className="grid grid-cols-2 gap-4 p-2">
{/* Status, Frequency, and Relevance in first column */}
<div>
<DropdownMenuLabel>Filter by Status</DropdownMenuLabel>
<div className="space-y-1 mb-3">
<DropdownMenuCheckboxItem
checked={status === "published"}
onCheckedChange={() => {
setStatus(status === "published" ? null : "published");
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<CheckCircle2 size={16} className="text-green-500" />
<span>Published</span>
</div>
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={status === "draft"}
onCheckedChange={() => {
setStatus(status === "draft" ? null : "draft");
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<XCircle size={16} className="text-red-500" />
<span>Draft</span>
</div>
</DropdownMenuCheckboxItem>
</div>

<DropdownMenuSeparator />
<DropdownMenuLabel>Filter by Relevance</DropdownMenuLabel>
<div className="space-y-1 mb-3">
<DropdownMenuCheckboxItem
checked={relevance === "relevant"}
onCheckedChange={() => {
setRelevance(relevance === "relevant" ? null : "relevant");
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<CheckCircle2 size={16} className="text-green-500" />
<span>Relevant</span>
</div>
</DropdownMenuCheckboxItem>
<DropdownMenuCheckboxItem
checked={relevance === "not-relevant"}
onCheckedChange={() => {
setRelevance(
relevance === "not-relevant" ? null : "not-relevant"
);
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<AlertTriangle size={16} className="text-yellow-500" />
<span>Not Relevant</span>
</div>
</DropdownMenuCheckboxItem>
</div>

<DropdownMenuLabel>Filter by Department</DropdownMenuLabel>
{departments.map((dept) => (
<DropdownMenuCheckboxItem
key={dept}
checked={department === dept}
onCheckedChange={() => {
setDepartment(department === dept ? null : dept);
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<Building size={16} className="text-muted-foreground" />
<span>{dept.replace(/_/g, " ").toUpperCase()}</span>
<DropdownMenuLabel>Filter by Frequency</DropdownMenuLabel>
<div className="space-y-1">
{frequencies.map((freq) => (
<DropdownMenuCheckboxItem
key={freq}
checked={frequency === freq}
onCheckedChange={() => {
setFrequency(frequency === freq ? null : freq);
setPage("1"); // Reset to first page when filtering
}}
>
{freq}
</DropdownMenuCheckboxItem>
))}
</div>
</DropdownMenuCheckboxItem>
))}
</div>

<DropdownMenuSeparator />
{/* Department and Assignee in second column */}
<div>
<DropdownMenuLabel>Filter by Department</DropdownMenuLabel>
<div className="space-y-1 mb-3 max-h-[150px] overflow-y-auto">
{departments.map((dept) => (
<DropdownMenuCheckboxItem
key={dept}
checked={department === dept}
onCheckedChange={() => {
setDepartment(department === dept ? null : dept);
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<Building size={16} className="text-muted-foreground" />
<span>{dept.replace(/_/g, " ").toUpperCase()}</span>
</div>
</DropdownMenuCheckboxItem>
))}
</div>

<DropdownMenuLabel>Filter by Assignee</DropdownMenuLabel>
{assignees.map((assignee) => (
<DropdownMenuCheckboxItem
key={assignee.id}
checked={assigneeId === assignee.id}
onCheckedChange={() => {
setAssigneeId(assigneeId === assignee.id ? null : assignee.id);
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<Avatar className="h-5 w-5">
<AvatarImage
src={assignee.image || undefined}
alt={assignee.name || ""}
/>
<AvatarFallback className="text-xs">
{assignee.name ? assignee.name.charAt(0) : "?"}
</AvatarFallback>
</Avatar>
<span>{assignee.name}</span>
<DropdownMenuLabel>Filter by Assignee</DropdownMenuLabel>
<div className="space-y-1 max-h-[150px] overflow-y-auto">
{assignees.map((assignee) => (
<DropdownMenuCheckboxItem
key={assignee.id}
checked={assigneeId === assignee.id}
onCheckedChange={() => {
setAssigneeId(
assigneeId === assignee.id ? null : assignee.id
);
setPage("1"); // Reset to first page when filtering
}}
>
<div className="flex items-center gap-2">
<Avatar className="h-5 w-5">
<AvatarImage
src={assignee.image || undefined}
alt={assignee.name || ""}
/>
<AvatarFallback className="text-xs">
{assignee.name ? assignee.name.charAt(0) : "?"}
</AvatarFallback>
</Avatar>
<span>{assignee.name}</span>
</div>
</DropdownMenuCheckboxItem>
))}
</div>
</DropdownMenuCheckboxItem>
))}
</div>
</div>

{hasActiveFilters && (
<>
<div className="px-2 pb-2">
<DropdownMenuSeparator />
<Button
variant="ghost"
size="sm"
className="w-full justify-start text-sm font-normal"
className="w-full justify-start text-sm font-normal mt-2"
onClick={clearFilters}
>
Clear all filters
</Button>
</>
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { DataTable } from "./data-table/data-table";
import { DataTable } from "./data-table/EvidenceListTable";
import { useI18n } from "@/locales/client";
import { SkeletonTable } from "./SkeletonTable";
import {
Expand Down
Loading