Skip to content
Open
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
2 changes: 1 addition & 1 deletion ui/src/components/IssueProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ function PropertyPicker({
<PropertyRow label={label}>
<Popover open={open} onOpenChange={onOpenChange}>
<PopoverTrigger asChild>
<button className={btnCn}>{triggerContent}</button>
<button className={btnCn} onClick={() => onOpenChange(!open)}>{triggerContent}</button>
</PopoverTrigger>
<PopoverContent className={cn("p-1", popoverClassName)} align={popoverAlign} collisionPadding={16}>
{children}
Expand Down
60 changes: 55 additions & 5 deletions ui/src/components/NewIssueDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,9 @@ export function NewIssueDialog() {
// Popover states
const [statusOpen, setStatusOpen] = useState(false);
const [priorityOpen, setPriorityOpen] = useState(false);
const [labelsOpen, setLabelsOpen] = useState(false);
const [labelSearch, setLabelSearch] = useState("");
const [selectedLabelIds, setSelectedLabelIds] = useState<string[]>([]);
const [moreOpen, setMoreOpen] = useState(false);
const [companyOpen, setCompanyOpen] = useState(false);
const descriptionEditorRef = useRef<MarkdownEditorRef>(null);
Expand All @@ -324,6 +327,12 @@ export function NewIssueDialog() {
queryFn: () => projectsApi.list(effectiveCompanyId!),
enabled: !!effectiveCompanyId && newIssueOpen,
});

const { data: labels } = useQuery({
queryKey: queryKeys.issues.labels(effectiveCompanyId!),
queryFn: () => issuesApi.listLabels(effectiveCompanyId!),
enabled: !!effectiveCompanyId && newIssueOpen,
});
const { data: reusableExecutionWorkspaces } = useQuery({
queryKey: queryKeys.executionWorkspaces.list(effectiveCompanyId!, {
projectId,
Expand Down Expand Up @@ -675,6 +684,7 @@ export function NewIssueDialog() {
? { executionWorkspaceId: selectedExecutionWorkspaceId }
: {}),
...(executionWorkspaceSettings ? { executionWorkspaceSettings } : {}),
...(selectedLabelIds.length > 0 ? { labelIds: selectedLabelIds } : {}),
});
}

Expand Down Expand Up @@ -1392,11 +1402,51 @@ export function NewIssueDialog() {
</PopoverContent>
</Popover>

{/* Labels chip (placeholder) */}
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors text-muted-foreground">
<Tag className="h-3 w-3" />
Labels
</button>
{/* Labels chip */}
<Popover open={labelsOpen} onOpenChange={(open) => { setLabelsOpen(open); if (!open) setLabelSearch(""); }}>
<PopoverTrigger asChild>
<button className="inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 text-xs hover:bg-accent/50 transition-colors text-muted-foreground" onClick={() => setLabelsOpen(!labelsOpen)}>
<Tag className="h-3 w-3" />
{selectedLabelIds.length > 0
? (labels ?? []).filter((l) => selectedLabelIds.includes(l.id)).map((l) => (
<span key={l.id} className="inline-flex items-center gap-1">
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: l.color }} />
{l.name}
</span>
))
: "Labels"}
</button>
</PopoverTrigger>
<PopoverContent className="w-52 p-1" align="start">
<input
className="w-full px-2 py-1.5 text-xs bg-transparent outline-none border-b border-border mb-1 placeholder:text-muted-foreground/50"
placeholder="Search labels..."
value={labelSearch}
onChange={(e) => setLabelSearch(e.target.value)}
autoFocus
/>
<div className="max-h-44 overflow-y-auto space-y-0.5">
{(labels ?? [])
.filter((l) => !labelSearch.trim() || l.name.toLowerCase().includes(labelSearch.toLowerCase()))
.map((label) => {
const selected = selectedLabelIds.includes(label.id);
return (
<button
key={label.id}
className={cn("flex items-center gap-2 w-full px-2 py-1.5 text-xs rounded hover:bg-accent/50 text-left", selected && "bg-accent")}
onClick={() => setSelectedLabelIds(selected ? selectedLabelIds.filter((id) => id !== label.id) : [...selectedLabelIds, label.id])}
>
<span className="h-2.5 w-2.5 rounded-full shrink-0" style={{ backgroundColor: label.color }} />
<span className="truncate">{label.name}</span>
</button>
);
})}
{(labels ?? []).length === 0 && (
<p className="px-2 py-2 text-xs text-muted-foreground">No labels defined.</p>
)}
Comment on lines +1444 to +1446
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Empty-state message never shows when search has no matches

The empty-state check (labels ?? []).length === 0 tests whether the unfiltered label list is empty. When the user types a search term that matches no labels, the filtered .map(...) above renders nothing — but this condition is still false (e.g. there are 5 labels, just none matching the search), so the user sees a completely blank dropdown with no feedback.

The guard should compare against the filtered list:

Suggested change
{(labels ?? []).length === 0 && (
<p className="px-2 py-2 text-xs text-muted-foreground">No labels defined.</p>
)}
{(labels ?? [])
.filter((l) => !labelSearch.trim() || l.name.toLowerCase().includes(labelSearch.toLowerCase()))
.length === 0 && (
<p className="px-2 py-2 text-xs text-muted-foreground">
{labelSearch.trim() ? "No labels found." : "No labels defined."}
</p>
)}
Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/components/NewIssueDialog.tsx
Line: 1444-1446

Comment:
**Empty-state message never shows when search has no matches**

The empty-state check `(labels ?? []).length === 0` tests whether the *unfiltered* label list is empty. When the user types a search term that matches no labels, the filtered `.map(...)` above renders nothing — but this condition is still false (e.g. there are 5 labels, just none matching the search), so the user sees a completely blank dropdown with no feedback.

The guard should compare against the *filtered* list:

```suggestion
                {(labels ?? [])
                  .filter((l) => !labelSearch.trim() || l.name.toLowerCase().includes(labelSearch.toLowerCase()))
                  .length === 0 && (
                  <p className="px-2 py-2 text-xs text-muted-foreground">
                    {labelSearch.trim() ? "No labels found." : "No labels defined."}
                  </p>
                )}
```

How can I resolve this? If you propose a fix, please make it concise.

</div>
</PopoverContent>
</Popover>

<input
ref={stageFileInputRef}
Expand Down
Loading