feat: Add project filter to listInitiatives
Add findByProjectId to InitiativeRepository using a subquery on the initiative_projects junction table. Extend the listInitiatives tRPC procedure to accept an optional projectId filter that composes with the existing status filter. Add a project dropdown to the initiatives page alongside the status filter.
This commit is contained in:
@@ -8,18 +8,27 @@ import { trpc } from "@/lib/trpc";
|
||||
|
||||
interface InitiativeListProps {
|
||||
statusFilter?: "all" | "active" | "completed" | "archived";
|
||||
projectFilter?: string;
|
||||
onCreateNew: () => void;
|
||||
onViewInitiative: (id: string) => void;
|
||||
}
|
||||
|
||||
export function InitiativeList({
|
||||
statusFilter = "all",
|
||||
projectFilter,
|
||||
onCreateNew,
|
||||
onViewInitiative,
|
||||
}: InitiativeListProps) {
|
||||
const initiativesQuery = trpc.listInitiatives.useQuery(
|
||||
statusFilter === "all" ? undefined : { status: statusFilter },
|
||||
);
|
||||
const queryInput = (() => {
|
||||
const hasStatus = statusFilter !== "all";
|
||||
if (!hasStatus && !projectFilter) return undefined;
|
||||
return {
|
||||
...(hasStatus && { status: statusFilter as "active" | "completed" | "archived" }),
|
||||
...(projectFilter && { projectId: projectFilter }),
|
||||
};
|
||||
})();
|
||||
|
||||
const initiativesQuery = trpc.listInitiatives.useQuery(queryInput);
|
||||
|
||||
// Loading state
|
||||
if (initiativesQuery.isLoading) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { InitiativeList } from "@/components/InitiativeList";
|
||||
import { CreateInitiativeDialog } from "@/components/CreateInitiativeDialog";
|
||||
import { useLiveUpdates } from "@/hooks";
|
||||
import { trpc } from "@/lib/trpc";
|
||||
|
||||
export const Route = createFileRoute("/initiatives/")({
|
||||
component: DashboardPage,
|
||||
@@ -23,7 +24,9 @@ const filterOptions: { value: StatusFilter; label: string }[] = [
|
||||
function DashboardPage() {
|
||||
const navigate = useNavigate();
|
||||
const [statusFilter, setStatusFilter] = useState<StatusFilter>("all");
|
||||
const [projectFilter, setProjectFilter] = useState<string>("all");
|
||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||
const projectsQuery = trpc.listProjects.useQuery();
|
||||
|
||||
// Single SSE stream for live updates
|
||||
useLiveUpdates([
|
||||
@@ -42,6 +45,18 @@ function DashboardPage() {
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="font-display text-2xl font-semibold">Initiatives</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<select
|
||||
value={projectFilter}
|
||||
onChange={(e) => setProjectFilter(e.target.value)}
|
||||
className="rounded-md border border-input bg-background px-3 py-1.5 text-sm ring-offset-background focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
|
||||
>
|
||||
<option value="all">All projects</option>
|
||||
{(projectsQuery.data ?? []).map((p) => (
|
||||
<option key={p.id} value={p.id}>
|
||||
{p.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) =>
|
||||
@@ -70,6 +85,7 @@ function DashboardPage() {
|
||||
>
|
||||
<InitiativeList
|
||||
statusFilter={statusFilter}
|
||||
projectFilter={projectFilter === "all" ? undefined : projectFilter}
|
||||
onCreateNew={() => setCreateDialogOpen(true)}
|
||||
onViewInitiative={(id) =>
|
||||
navigate({ to: "/initiatives/$id", params: { id } })
|
||||
|
||||
Reference in New Issue
Block a user