mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 21:33:41 +02:00
* feat(projects): add start_date / due_date pickers to project create modal and sidebar #5313 landed the backend start_date/due_date fields + types but deliberately shipped no UI. Wire up the two editor surfaces users expect: - ProjectStartDatePicker / ProjectDueDatePicker mirror the issue pickers (same calendar-day contract, clear idiom, shared @multica/core/issues/date helpers) but are typed to UpdateProjectRequest and scoped to the "projects" i18n namespace. One component serves both surfaces via a custom trigger. - Create-project modal: two date pills; values flow into the create payload and the persisted draft (draft-store gains startDate/dueDate). - Project sidebar (project-detail): two PropRows after Lead, wired to the update mutation, with clear support (send null). - i18n: prop_start_date / prop_due_date / clear_date across en/zh-Hans/ja/ko, reusing the existing issue date wording. Tests: picker display + clear behavior (real popover), and the create modal renders both pills. typecheck + lint + i18n parity pass. Part of #5227 Co-authored-by: multica-agent <github@multica.ai> * refactor(projects): align create-project footer with create-issue Restructure the create-project modal footer to match the create-issue pattern (per design feedback): the primary action moves out of the cramped single justify-between row into its own border-t action strip, and the property pills sit in a dedicated wrapping toolbar above it. Low-frequency fields (start/due date) collapse into a ⋯ overflow via progressive disclosure — a pill only renders inline once its date is set or the user opens it from the menu — so the default toolbar stays a clean single row (Status · Priority · Lead · Repos · ⋯). - Use the shared PillButton (../common/pill-button) instead of the modal-local copy, gaining the data-popup-open styling create-issue uses. - ProjectStartDatePicker / ProjectDueDatePicker gain controlled open props so the overflow menu can reveal + open them (mirrors the issue pickers). - i18n: create_project.set_start_date / set_due_date / more_options_aria across en / zh-Hans / ja / ko, reusing the create-issue wording. Test updated to assert the dates are revealed from the overflow rather than shown inline by default. typecheck / lint / i18n parity pass. Part of #5227 Co-authored-by: multica-agent <github@multica.ai> * refactor(views): extract shared DateOnlyPicker base for date pills (Elon nit2) The issue and project start/due-date pickers were near-complete copies of the same Popover + Calendar + clear wiring, so they could drift in behaviour or formatting. Extract that into one entity-agnostic DateOnlyPicker (packages/views/common/date-only-picker.tsx); each of the four pills is now a thin wrapper supplying only its field name (via onChange), icon, overdue flag, and localized copy. -264 lines of duplication, single source of truth. Behaviour is unchanged: the issue pickers keep their full API (trigger / triggerRender / open / onOpenChange / align / defaultOpen — all still used by board-card, issue-detail, create-issue) and the calendar-day contract stays in @multica/core/issues/date. The en-US display format now lives in one place rather than being duplicated per entity. Full views test suite (1857 tests) + typecheck + lint pass. Part of #5227 Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: J <j@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
55 lines
2.2 KiB
TypeScript
55 lines
2.2 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { renderWithI18n } from "../../test/i18n";
|
|
import { ProjectStartDatePicker } from "./project-start-date-picker";
|
|
import { ProjectDueDatePicker } from "./project-due-date-picker";
|
|
|
|
describe("ProjectStartDatePicker", () => {
|
|
it("shows the placeholder label when no date is set", () => {
|
|
renderWithI18n(<ProjectStartDatePicker startDate={null} onUpdate={vi.fn()} />);
|
|
expect(screen.getByText("Start date")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows the formatted calendar day when a date is set", () => {
|
|
renderWithI18n(
|
|
<ProjectStartDatePicker startDate="2026-03-01" onUpdate={vi.fn()} />,
|
|
);
|
|
expect(screen.getByText("Mar 1")).toBeInTheDocument();
|
|
});
|
|
|
|
it("emits start_date: null when the date is cleared", async () => {
|
|
const onUpdate = vi.fn();
|
|
const user = userEvent.setup();
|
|
renderWithI18n(
|
|
<ProjectStartDatePicker startDate="2026-03-01" onUpdate={onUpdate} />,
|
|
);
|
|
await user.click(screen.getByText("Mar 1")); // open popover
|
|
await user.click(screen.getByRole("button", { name: "Clear date" }));
|
|
expect(onUpdate).toHaveBeenCalledWith({ start_date: null });
|
|
});
|
|
});
|
|
|
|
describe("ProjectDueDatePicker", () => {
|
|
it("shows the placeholder label when no date is set", () => {
|
|
renderWithI18n(<ProjectDueDatePicker dueDate={null} onUpdate={vi.fn()} />);
|
|
expect(screen.getByText("Due date")).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows the formatted calendar day when a date is set", () => {
|
|
renderWithI18n(<ProjectDueDatePicker dueDate="2026-03-01" onUpdate={vi.fn()} />);
|
|
expect(screen.getByText("Mar 1")).toBeInTheDocument();
|
|
});
|
|
|
|
it("emits due_date: null when the date is cleared", async () => {
|
|
const onUpdate = vi.fn();
|
|
const user = userEvent.setup();
|
|
renderWithI18n(
|
|
<ProjectDueDatePicker dueDate="2026-03-01" onUpdate={onUpdate} />,
|
|
);
|
|
await user.click(screen.getByText("Mar 1")); // open popover
|
|
await user.click(screen.getByRole("button", { name: "Clear date" }));
|
|
expect(onUpdate).toHaveBeenCalledWith({ due_date: null });
|
|
});
|
|
});
|