;
+ selected: ActorFilterValue[];
+ onToggle: (value: ActorFilterValue) => void;
+ showNoAssignee?: boolean;
+ includeNoAssignee?: boolean;
+ onToggleNoAssignee?: () => void;
+ noAssigneeCount?: number;
+}) {
+ const [search, setSearch] = useState("");
+ const members = useWorkspaceStore((s) => s.members);
+ const agents = useWorkspaceStore((s) => s.agents);
+ const { getActorInitials } = useActorName();
+
+ const query = search.toLowerCase();
+ const filteredMembers = members.filter((m) =>
+ m.name.toLowerCase().includes(query),
+ );
+ const filteredAgents = agents.filter((a) =>
+ a.name.toLowerCase().includes(query),
+ );
+
+ const isSelected = (type: "member" | "agent", id: string) =>
+ selected.some((f) => f.type === type && f.id === id);
+
+ return (
+ <>
+
+ setSearch(e.target.value)}
+ placeholder="Filter..."
+ className="w-full bg-transparent text-sm placeholder:text-muted-foreground outline-none"
+ autoFocus
+ />
+
+
+
+ {showNoAssignee &&
+ (!query || "no assignee".includes(query) || "unassigned".includes(query)) && (
+
onToggleNoAssignee?.()}
+ className={FILTER_ITEM_CLASS}
+ >
+
+
+ No assignee
+ {(noAssigneeCount ?? 0) > 0 && (
+
+ {noAssigneeCount}
+
+ )}
+
+ )}
+
+ {filteredMembers.length > 0 && (
+
+ Members
+ {filteredMembers.map((m) => {
+ const checked = isSelected("member", m.user_id);
+ const count = counts.get(`member:${m.user_id}`) ?? 0;
+ return (
+
+ onToggle({ type: "member", id: m.user_id })
+ }
+ className={FILTER_ITEM_CLASS}
+ >
+
+
+ {getActorInitials("member", m.user_id)}
+
+ {m.name}
+ {count > 0 && (
+
+ {count}
+
+ )}
+
+ );
+ })}
+
+ )}
+
+ {filteredAgents.length > 0 && (
+
+ Agents
+ {filteredAgents.map((a) => {
+ const checked = isSelected("agent", a.id);
+ const count = counts.get(`agent:${a.id}`) ?? 0;
+ return (
+
+ onToggle({ type: "agent", id: a.id })
+ }
+ className={FILTER_ITEM_CLASS}
+ >
+
+
+
+
+ {a.name}
+ {count > 0 && (
+
+ {count}
+
+ )}
+
+ );
+ })}
+
+ )}
+
+ {filteredMembers.length === 0 && filteredAgents.length === 0 && search && (
+
+ No results
+
+ )}
+
+ >
+ );
+}
+
+// ---------------------------------------------------------------------------
+// IssuesHeader
+// ---------------------------------------------------------------------------
export function IssuesHeader() {
const viewMode = useIssueViewStore((s) => s.viewMode);
const statusFilters = useIssueViewStore((s) => s.statusFilters);
const priorityFilters = useIssueViewStore((s) => s.priorityFilters);
+ const assigneeFilters = useIssueViewStore((s) => s.assigneeFilters);
+ const includeNoAssignee = useIssueViewStore((s) => s.includeNoAssignee);
+ const creatorFilters = useIssueViewStore((s) => s.creatorFilters);
const sortBy = useIssueViewStore((s) => s.sortBy);
const sortDirection = useIssueViewStore((s) => s.sortDirection);
const cardProperties = useIssueViewStore((s) => s.cardProperties);
const setViewMode = useIssueViewStore((s) => s.setViewMode);
const toggleStatusFilter = useIssueViewStore((s) => s.toggleStatusFilter);
const togglePriorityFilter = useIssueViewStore((s) => s.togglePriorityFilter);
+ const toggleAssigneeFilter = useIssueViewStore((s) => s.toggleAssigneeFilter);
+ const toggleNoAssignee = useIssueViewStore((s) => s.toggleNoAssignee);
+ const toggleCreatorFilter = useIssueViewStore((s) => s.toggleCreatorFilter);
+ const clearFilters = useIssueViewStore((s) => s.clearFilters);
const setSortBy = useIssueViewStore((s) => s.setSortBy);
const setSortDirection = useIssueViewStore((s) => s.setSortDirection);
const toggleCardProperty = useIssueViewStore((s) => s.toggleCardProperty);
- const clearFilters = useIssueViewStore((s) => s.clearFilters);
const allIssues = useIssueStore((s) => s.issues);
+ const counts = useIssueCounts(allIssues);
- const filteredCount = useMemo(() => {
- return allIssues.filter((i) => {
- if (statusFilters.length > 0 && !statusFilters.includes(i.status))
- return false;
- if (
- priorityFilters.length > 0 &&
- !priorityFilters.includes(i.priority)
- )
- return false;
- return true;
- }).length;
- }, [allIssues, statusFilters, priorityFilters]);
+ const filteredCount = useMemo(
+ () => filterIssues(allIssues, { statusFilters, priorityFilters, assigneeFilters, includeNoAssignee, creatorFilters }).length,
+ [allIssues, statusFilters, priorityFilters, assigneeFilters, includeNoAssignee, creatorFilters],
+ );
+
+ const filterCount = getActiveFilterCount({
+ statusFilters,
+ priorityFilters,
+ assigneeFilters,
+ includeNoAssignee,
+ creatorFilters,
+ });
const sortLabel =
SORT_OPTIONS.find((o) => o.value === sortBy)?.label ?? "Manual";
- const hasActiveFilters =
- statusFilters.length > 0 || priorityFilters.length > 0;
+ const hasActiveFilters = filterCount > 0;
return (
@@ -106,9 +337,9 @@ export function IssuesHeader() {
- {/* Filter */}
-
-
+
- {statusFilters.length + priorityFilters.length}
+ {filterCount}
)}
}
/>
-
+
{/* Status */}
-
-
- Status
-
-
- {ALL_STATUSES.map((s) => (
-
- ))}
-
-
+
+ );
+ })}
+
+
{/* Priority */}
-
-
- Priority
-
-
- {PRIORITY_ORDER.map((p) => (
-
- ))}
-
-
+
+ );
+ })}
+
+
+
+ {/* Assignee */}
+
+
+
+ Assignee
+ {(assigneeFilters.length > 0 || includeNoAssignee) && (
+
+ {assigneeFilters.length + (includeNoAssignee ? 1 : 0)}
+
+ )}
+
+
+
+
+
+
+ {/* Creator */}
+
+
+
+ Creator
+ {creatorFilters.length > 0 && (
+
+ {creatorFilters.length}
+
+ )}
+
+
+
+
+
{/* Reset */}
{hasActiveFilters && (
-
-
-
+ <>
+
+
+ Reset all filters
+
+ >
)}
-
-
+
+
{/* Display settings */}
@@ -232,7 +496,6 @@ export function IssuesHeader() {
}
/>
- {/* Ordering section */}
Ordering
@@ -279,7 +542,6 @@ export function IssuesHeader() {
- {/* Card properties section */}
Card properties
@@ -308,7 +570,6 @@ export function IssuesHeader() {
{filteredCount} {filteredCount === 1 ? "Issue" : "Issues"}
- {/* New issue */}