Timeline: default filter to "all" + add missing statuses + team scope

Three fixes for an empty Production Timeline:

1. Default statusFilter flipped from ACTIVE → "all". Fresh imports
   land at PIPELINE status (brief-accepted), so ACTIVE-default hid
   every project on first load.
2. Status dropdown was missing PIPELINE and CANCELED — so even
   after you realised ACTIVE was wrong you couldn't pick PIPELINE.
   Added both.
3. /api/timeline was scoping by organizationId only, not ClientTeam
   visibility. Non-ADMIN users would have seen cross-team projects.
   AND-in visibleProjectsWhere(ctx) now — same pattern as the rest
   of the service layer.
This commit is contained in:
DJP 2026-04-21 14:11:16 -04:00
parent 4fde413aa9
commit 0ba7c2b464
2 changed files with 17 additions and 6 deletions

View file

@ -41,7 +41,9 @@ const ZOOM_LABELS: Record<ZoomLevel, string> = {
export default function TimelinePage() {
const [zoom, setZoom] = useState<ZoomLevel>("day");
const [statusFilter, setStatusFilter] = useState<string>("ACTIVE");
// Default to "all" — previously ACTIVE, which hid every PIPELINE project
// (i.e. everything freshly imported) and left the timeline empty.
const [statusFilter, setStatusFilter] = useState<string>("all");
const [priorityFilter, setPriorityFilter] = useState<string>("all");
const { data: projects, isLoading } = useTimeline({
@ -205,9 +207,11 @@ export default function TimelinePage() {
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Statuses</SelectItem>
<SelectItem value="PIPELINE">Pipeline</SelectItem>
<SelectItem value="ACTIVE">Active</SelectItem>
<SelectItem value="ON_HOLD">On Hold</SelectItem>
<SelectItem value="COMPLETED">Completed</SelectItem>
<SelectItem value="CANCELED">Canceled</SelectItem>
<SelectItem value="ARCHIVED">Archived</SelectItem>
</SelectContent>
</Select>

View file

@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from "next/server";
import { serverError } from "@/lib/api-utils";
import { requireAuth } from "@/lib/rbac/require-auth";
import { requireAuth, visibilityContextFromSession } from "@/lib/rbac/require-auth";
import { visibleProjectsWhere } from "@/lib/rbac/visibility";
import { prisma } from "@/lib/prisma";
// GET /api/timeline?status=ACTIVE&priority=&projectIds=
@ -16,13 +17,19 @@ export async function GET(request: NextRequest) {
const projectIds = projectIdsParam ? projectIdsParam.split(",") : undefined;
const organizationId = session.user.organizationId;
const ctx = visibilityContextFromSession(session);
// Build project filter
const projectWhere: any = { organizationId };
if (statusFilter) projectWhere.status = statusFilter;
// Build project filter — AND-in the team visibility scope so non-ADMIN
// users only see projects on the ClientTeams they belong to (previously
// the timeline leaked cross-team data).
const baseFilter: any = { organizationId };
if (statusFilter) baseFilter.status = statusFilter;
if (projectIds && projectIds.length > 0) {
projectWhere.id = { in: projectIds };
baseFilter.id = { in: projectIds };
}
const projectWhere: any = {
AND: [baseFilter, await visibleProjectsWhere(ctx)],
};
const delivWhere: any = {};
if (priorityFilter) delivWhere.priority = priorityFilter;