Files
multica/packages/views/issues/components/pickers/status-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

75 lines
2.1 KiB
TypeScript

"use client";
import { useState } from "react";
import type { IssueStatus, UpdateIssueRequest } from "@multica/core/types";
import { ALL_STATUSES, STATUS_CONFIG } from "@multica/core/issues/config";
import { StatusIcon } from "../status-icon";
import { PropertyPicker, PickerItem } from "./property-picker";
import { useT } from "../../../i18n";
export function StatusPicker({
status,
onUpdate,
trigger: customTrigger,
triggerRender,
open: controlledOpen,
onOpenChange: controlledOnOpenChange,
align,
}: {
/**
* The currently-selected status, used to check the matching row. `null`
* means "no single current value" (e.g. a batch selection spanning several
* statuses) — no row is checked. Single-issue callers always pass a concrete
* status.
*/
status: IssueStatus | null;
onUpdate: (updates: Partial<UpdateIssueRequest>) => void;
trigger?: React.ReactNode;
triggerRender?: React.ReactElement;
open?: boolean;
onOpenChange?: (v: boolean) => void;
align?: "start" | "center" | "end";
}) {
const [internalOpen, setInternalOpen] = useState(false);
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 ??
(status != null ? (
<>
<StatusIcon status={status} className="h-3.5 w-3.5 shrink-0" />
<span className="truncate">{t(($) => $.status[status])}</span>
</>
) : null)
}
>
{ALL_STATUSES.map((s) => {
const c = STATUS_CONFIG[s];
return (
<PickerItem
key={s}
selected={s === status}
hoverClassName={c.hoverBg}
onClick={() => {
onUpdate({ status: s });
setOpen(false);
}}
>
<StatusIcon status={s} className="h-3.5 w-3.5" />
<span>{t(($) => $.status[s])}</span>
</PickerItem>
);
})}
</PropertyPicker>
);
}