Files
multica/packages/views/issues/components/pickers/priority-picker.tsx
Naiyuan Qing 149cc9bd0a fix(issues): reflect real common value in batch toolbar pickers (#4403)
The batch action toolbar hardcoded status="todo", priority="none", and a
null assignee, so the status/priority/assignee pickers always checked a
fixed row regardless of the selected issues. The batch write itself worked,
but the picker mis-reported the current value, surfacing as "status always
defaults to todo" (MUL-3510). The same defect applied to priority and
assignee, across all five toolbar mount points.

Derive the shared status/priority/assignee of the selected issues via a new
commonIssueFields helper and feed it to the pickers; when the selection is
mixed, pass an empty value so no row is checked. Pickers now accept a
nullable current value, and AssigneePicker gains a `mixed` flag to
distinguish an all-unassigned selection (check "No assignee") from a mixed
one (check nothing). Each call site passes its issue universe, mirroring the
skill list's selected-rows approach.

Adds unit tests for commonIssueFields and a toolbar picker-wiring test.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
2026-06-22 18:23:05 +08:00

80 lines
2.5 KiB
TypeScript

"use client";
import { useState } from "react";
import type { IssuePriority, UpdateIssueRequest } from "@multica/core/types";
import { PRIORITY_ORDER, PRIORITY_CONFIG } from "@multica/core/issues/config";
import { PriorityIcon } from "../priority-icon";
import { PropertyPicker, PickerItem } from "./property-picker";
import { useT } from "../../../i18n";
export function PriorityPicker({
priority,
onUpdate,
trigger: customTrigger,
triggerRender,
open: controlledOpen,
onOpenChange: controlledOnOpenChange,
align,
defaultOpen = false,
}: {
/**
* The currently-selected priority, used to check the matching row. `null`
* means "no single current value" (e.g. a batch selection spanning several
* priorities) — no row is checked. Single-issue callers always pass a
* concrete priority.
*/
priority: IssuePriority | null;
onUpdate: (updates: Partial<UpdateIssueRequest>) => void;
trigger?: React.ReactNode;
triggerRender?: React.ReactElement;
open?: boolean;
onOpenChange?: (v: boolean) => void;
align?: "start" | "center" | "end";
/** Open the picker on first mount. Used by progressive-disclosure
* sidebars so a newly-added field immediately enters edit state. */
defaultOpen?: boolean;
}) {
const [internalOpen, setInternalOpen] = useState(defaultOpen);
const open = controlledOpen ?? internalOpen;
const setOpen = controlledOnOpenChange ?? setInternalOpen;
const { t } = useT("issues");
return (
<PropertyPicker
open={open}
onOpenChange={setOpen}
width="w-44"
align={align}
triggerRender={triggerRender}
trigger={
customTrigger ??
(priority != null ? (
<>
<PriorityIcon priority={priority} className="shrink-0" />
<span className="truncate">{t(($) => $.priority[priority])}</span>
</>
) : null)
}
>
{PRIORITY_ORDER.map((p) => {
const c = PRIORITY_CONFIG[p];
return (
<PickerItem
key={p}
selected={p === priority}
onClick={() => {
onUpdate({ priority: p });
setOpen(false);
}}
>
<span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${c.badgeBg} ${c.badgeText}`}>
<PriorityIcon priority={p} className="h-3 w-3" inheritColor />
{t(($) => $.priority[p])}
</span>
</PickerItem>
);
})}
</PropertyPicker>
);
}