diff --git a/apps/client/pages/issues/index.tsx b/apps/client/pages/issues/index.tsx
index af5df548c..ee68af36e 100644
--- a/apps/client/pages/issues/index.tsx
+++ b/apps/client/pages/issues/index.tsx
@@ -1,7 +1,7 @@
import useTranslation from "next-translate/useTranslation";
import { useRouter } from "next/router";
import Loader from "react-spinners/ClipLoader";
-import { useState } from "react";
+import { useState, useMemo } from "react";
import { ContextMenu } from "@radix-ui/themes";
import { getCookie } from "cookies-next";
@@ -10,7 +10,7 @@ import Link from "next/link";
import { useQuery } from "react-query";
import { useUser } from "../../store/session";
import { Popover, PopoverContent, PopoverTrigger } from "@/shadcn/ui/popover";
-import { CheckIcon, PlusCircle } from "lucide-react";
+import { CheckIcon, Filter, PlusCircle, X } from "lucide-react";
import { Button } from "@/shadcn/ui/button";
import {
Command,
@@ -32,6 +32,28 @@ async function getUserTickets(token: any) {
return res.json();
}
+// Add this new component for the filter badge
+const FilterBadge = ({
+ text,
+ onRemove,
+}: {
+ text: string;
+ onRemove: () => void;
+}) => (
+
+ {text}
+
+
+);
+
export default function Tickets() {
const router = useRouter();
const { t } = useTranslation("peppermint");
@@ -47,7 +69,10 @@ export default function Tickets() {
const low = "bg-blue-100 text-blue-800";
const normal = "bg-green-100 text-green-800";
+ const [filterSelected, setFilterSelected] = useState();
const [selectedPriorities, setSelectedPriorities] = useState([]);
+ const [selectedStatuses, setSelectedStatuses] = useState([]);
+ const [selectedAssignees, setSelectedAssignees] = useState([]);
const handlePriorityToggle = (priority: string) => {
setSelectedPriorities((prev) =>
@@ -57,14 +82,65 @@ export default function Tickets() {
);
};
+ const handleStatusToggle = (status: string) => {
+ setSelectedStatuses((prev) =>
+ prev.includes(status)
+ ? prev.filter((s) => s !== status)
+ : [...prev, status]
+ );
+ };
+
+ const handleAssigneeToggle = (assignee: string) => {
+ setSelectedAssignees((prev) =>
+ prev.includes(assignee)
+ ? prev.filter((a) => a !== assignee)
+ : [...prev, assignee]
+ );
+ };
+
const filteredTickets = data
- ? data.tickets.filter((ticket) =>
- selectedPriorities.length > 0
- ? selectedPriorities.includes(ticket.priority)
- : true
- )
+ ? data.tickets.filter((ticket) => {
+ const priorityMatch =
+ selectedPriorities.length === 0 ||
+ selectedPriorities.includes(ticket.priority);
+ const statusMatch =
+ selectedStatuses.length === 0 ||
+ selectedStatuses.includes(ticket.isComplete ? "closed" : "open");
+ const assigneeMatch =
+ selectedAssignees.length === 0 ||
+ selectedAssignees.includes(ticket.assignedTo?.name || "Unassigned");
+
+ return priorityMatch && statusMatch && assigneeMatch;
+ })
: [];
+ type FilterType = "priority" | "status" | "assignee" | null;
+ const [activeFilter, setActiveFilter] = useState(null);
+ const [filterSearch, setFilterSearch] = useState("");
+
+ const filteredPriorities = useMemo(() => {
+ const priorities = ["low", "medium", "high"];
+ return priorities.filter((priority) =>
+ priority.toLowerCase().includes(filterSearch.toLowerCase())
+ );
+ }, [filterSearch]);
+
+ const filteredStatuses = useMemo(() => {
+ const statuses = ["open", "closed"];
+ return statuses.filter((status) =>
+ status.toLowerCase().includes(filterSearch.toLowerCase())
+ );
+ }, [filterSearch]);
+
+ const filteredAssignees = useMemo(() => {
+ const assignees = data?.tickets
+ .map((t) => t.assignedTo?.name || "Unassigned")
+ .filter((name, index, self) => self.indexOf(name) === index);
+ return assignees?.filter((assignee) =>
+ assignee.toLowerCase().includes(filterSearch.toLowerCase())
+ );
+ }, [data?.tickets, filterSearch]);
+
return (
{status === "loading" && (
@@ -76,60 +152,221 @@ export default function Tickets() {
{status === "success" && (
-
-
-
All Tickets
+
+
-
-
-
-
- No results found.
-
- {["low", "medium", "high"].map((priority) => (
+
+ {!activeFilter ? (
+
+
+
+ No results found.
+
+ setActiveFilter("priority")}
+ >
+ Priority
+
+ setActiveFilter("status")}
+ >
+ Status
+
+ setActiveFilter("assignee")}
+ >
+ Assigned To
+
+
+
+
+ ) : activeFilter === "priority" ? (
+
+
+
+ No priorities found.
+
+ {filteredPriorities.map((priority) => (
+ handlePriorityToggle(priority)}
+ >
+
+
+
+ {priority}
+
+ ))}
+
+
+
handlePriorityToggle(priority)}
+ onSelect={() => {
+ setActiveFilter(null);
+ setFilterSearch("");
+ }}
+ className="justify-center text-center"
>
-
+
+
+
+ ) : activeFilter === "status" ? (
+
+
+
+ No statuses found.
+
+ {filteredStatuses.map((status) => (
+ handleStatusToggle(status)}
>
-
-
- {priority}
+
+
+
+ {status}
+
+ ))}
+
+
+
+ {
+ setActiveFilter(null);
+ setFilterSearch("");
+ }}
+ className="justify-center text-center"
+ >
+ Back to filters
- ))}
-
- <>
+
+
+
+ ) : activeFilter === "assignee" ? (
+
+
+
+ No assignees found.
+
+ {filteredAssignees?.map((name) => (
+ handleAssigneeToggle(name)}
+ >
+
+
+
+ {name}
+
+ ))}
+
setSelectedPriorities([])}
+ onSelect={() => {
+ setActiveFilter(null);
+ setFilterSearch("");
+ }}
className="justify-center text-center"
>
- Clear filters
+ Back to filters
- >
-
-
+
+
+ ) : null}
+
+ {/* Display selected filters */}
+
+ {selectedPriorities.map((priority) => (
+ handlePriorityToggle(priority)}
+ />
+ ))}
+
+ {selectedStatuses.map((status) => (
+ handleStatusToggle(status)}
+ />
+ ))}
+
+ {selectedAssignees.map((assignee) => (
+ handleAssigneeToggle(assignee)}
+ />
+ ))}
+
+ {/* Clear all filters button - only show if there are filters */}
+ {(selectedPriorities.length > 0 ||
+ selectedStatuses.length > 0 ||
+ selectedAssignees.length > 0) && (
+
+ )}
+