mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-29 06:28:23 +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>
75 lines
2.1 KiB
TypeScript
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>
|
|
);
|
|
}
|