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
10 changes: 10 additions & 0 deletions apps/mcp/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -425,11 +425,21 @@ export interface FilterConfig {
items?: Record<string, FilterEntry>;
}

export interface CustomFieldFilterConfig {
selector?: FilterConfig;
min?: number;
max?: number;
after?: string;
before?: string;
contains?: string;
}

export interface ViewFilters {
task_types?: FilterConfig;
statuses?: FilterConfig;
assignees?: FilterConfig;
sprints?: FilterConfig;
custom_fields?: Record<string, CustomFieldFilterConfig>;
}

export interface ViewConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { useDebouncedCallback } from "@/hooks/use-debounced-callback";
import {
allTasksQueryOptions,
bulkMoveViewTaskPositions,
type CustomFieldFilterQuery,
createSprint,
createTask,
createViewByContext,
Expand Down Expand Up @@ -76,6 +77,7 @@ import { cn } from "@/lib/utils";
import { BoardView } from "./board-view";
import { ListView } from "./list-view";
import { NewViewPopover } from "./new-view-popover";
import { getImportanceBucketBounds } from "./priority";
import { RenameViewDialog } from "./rename-view-dialog";
import { RoadmapView } from "./roadmap-view";
import { TaskDetailModal } from "./task-detail-modal";
Expand Down Expand Up @@ -561,6 +563,52 @@ export function InteractionLayout({
);
}

let custom_field_filters:
| Record<string, CustomFieldFilterQuery>
| undefined;
const cfFilterConfig = activeViewConfig?.filters?.custom_fields;
if (cfFilterConfig) {
const resolved: Record<string, CustomFieldFilterQuery> = {};
for (const cf of customFields) {
const f = cfFilterConfig[cf.field_key];
if (!f) continue;
if (cf.field_type === "select" || cf.field_type === "multi_select") {
if (!f.selector) continue;
const values = resolveFilterConfig(f.selector, cf.options);
if (values.length > 0) resolved[cf.field_key] = { values };
} else if (cf.field_type === "boolean") {
if (!f.selector) continue;
const values = resolveFilterConfig(f.selector, ["true", "false"]);
if (values.length > 0) resolved[cf.field_key] = { values };
} else if (cf.field_type === "number") {
if (f.min == null && f.max == null) continue;
resolved[cf.field_key] = { min: f.min, max: f.max };
} else if (cf.field_type === "date") {
if (!f.after && !f.before) continue;
resolved[cf.field_key] = { after: f.after, before: f.before };
} else {
// text / url
if (!f.contains) continue;
resolved[cf.field_key] = { contains: f.contains };
}
}
if (Object.keys(resolved).length > 0) custom_field_filters = resolved;
}

let importance_ranges: { min: number; max: number }[] | undefined;
const importanceBuckets = activeViewConfig?.filters?.importance;
if (importanceBuckets && importanceBuckets.length > 0) {
importance_ranges = importanceBuckets.map((bucket) =>
getImportanceBucketBounds(bucket),
);
}

const tags =
activeViewConfig?.filters?.tags &&
activeViewConfig.filters.tags.length > 0
? activeViewConfig.filters.tags
: undefined;

return {
sprint_ids:
activeViewConfig?.filters !== undefined
Expand All @@ -582,9 +630,19 @@ export function InteractionLayout({
assignee_ids,
assignee_null,
task_type_ids,
custom_field_filters,
start_date_after: activeViewConfig?.filters?.start_date?.after,
start_date_before: activeViewConfig?.filters?.start_date?.before,
due_date_after: activeViewConfig?.filters?.due_date?.after,
due_date_before: activeViewConfig?.filters?.due_date?.before,
story_points_min: activeViewConfig?.filters?.story_points?.min,
story_points_max: activeViewConfig?.filters?.story_points?.max,
importance_ranges,
tags,
};
}, [
activeViewConfig?.filters,
customFields,
defaultPageTaskTypeIds,
members,
sprints,
Expand Down Expand Up @@ -645,6 +703,15 @@ export function InteractionLayout({
sortBy: activeViewConfig?.sort_by,
viewId: effectiveViewId,
search: debouncedSearchQuery || undefined,
customFieldFilters: apiFilters.custom_field_filters,
startDateAfter: apiFilters.start_date_after,
startDateBefore: apiFilters.start_date_before,
dueDateAfter: apiFilters.due_date_after,
dueDateBefore: apiFilters.due_date_before,
storyPointsMin: apiFilters.story_points_min,
storyPointsMax: apiFilters.story_points_max,
importanceRanges: apiFilters.importance_ranges,
tags: apiFilters.tags,
}),
[
context,
Expand Down Expand Up @@ -724,6 +791,15 @@ export function InteractionLayout({
sortBy: activeViewConfig?.sort_by,
viewId: effectiveViewId,
search: debouncedSearchQuery || undefined,
customFieldFilters: apiFilters.custom_field_filters,
startDateAfter: apiFilters.start_date_after,
startDateBefore: apiFilters.start_date_before,
dueDateAfter: apiFilters.due_date_after,
dueDateBefore: apiFilters.due_date_before,
storyPointsMin: apiFilters.story_points_min,
storyPointsMax: apiFilters.story_points_max,
importanceRanges: apiFilters.importance_ranges,
tags: apiFilters.tags,
}),
[
context,
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/components/projects/interactions/priority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export function getImportanceBucketBounds(bucket: number): {
case 3:
return { min: 50, max: 99 };
default:
return { min: 100, max: Number.MAX_SAFE_INTEGER };
// Matches Postgres' `importance` column type (INTEGER, max 2147483647) —
// the API binds this value directly into a SQL integer comparison, so a
// larger bound (e.g. Number.MAX_SAFE_INTEGER) overflows and errors.
return { min: 100, max: 2147483647 };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ import {
ArrowRight,
BookOpen,
Check,
Clock,
ExternalLink,
KanbanSquare,
Layers,
Link2,
Plus,
X,
} from "lucide-react";
Expand All @@ -31,7 +29,7 @@ import {
} from "../priority";
import { TaskTypeSelector } from "../task-type-selector";
import { AddFieldDialog } from "./add-field-dialog";
import { FieldRow, FieldValue } from "./primitives";
import { FieldRow } from "./primitives";
import type { SelectOption, UserOption } from "./property-field";
import { PropertyField } from "./property-field";
import { NumberEditor } from "./property-field/number-editor";
Expand Down Expand Up @@ -163,16 +161,6 @@ export function PropertiesPanel({
canEdit={canEdit}
/>

<FieldRow label={t("taskDetail.properties.trackTime")}>
<button
type="button"
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground/70 hover:text-foreground transition-colors duration-150 font-medium"
>
<Clock className="size-3.5 opacity-70" />
{t("taskDetail.properties.addTime")}
</button>
</FieldRow>

{(taskType || (canEdit && taskTypes.length > 0)) && (
<FieldRow label={t("taskDetail.properties.type")}>
<TaskTypeSelector
Expand All @@ -184,16 +172,6 @@ export function PropertiesPanel({
</FieldRow>
)}

<FieldRow label={t("taskDetail.properties.relationships")}>
<button
type="button"
className="inline-flex items-center gap-1.5 text-sm text-muted-foreground/70 hover:text-foreground transition-colors duration-150 font-medium"
>
<Link2 className="size-3.5 opacity-70" />
<FieldValue empty />
</button>
</FieldRow>

<PropertyField
label={t("taskDetail.properties.assignees")}
mode="multi-user"
Expand Down
Loading
Loading