mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-12 00:45:55 +02:00
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>
80 lines
2.5 KiB
TypeScript
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>
|
|
);
|
|
}
|