feat(stock-quick-draft): modal skeleton + Stock Order Reference header

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
SJCoetz 2026-07-30 22:15:51 +02:00
parent 921ea4e63d
commit 2d4d0df6d1
2 changed files with 121 additions and 0 deletions

View file

@ -0,0 +1,55 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { QuickDraftModal } from "./quick-draft-modal";
// The modal uses ModalShell.Dialog which depends on @base-ui/react.
// The component test environment stubs it via a passthrough wrapper.
vi.mock("@/components/ui/modal-shell", () => ({
ModalShell: {
Dialog: ({ children, open }: { children: React.ReactNode; open: boolean }) =>
open ? <div data-testid="modal">{children}</div> : null,
Header: ({ children }: { children: React.ReactNode }) => <div data-testid="modal-header">{children}</div>,
Body: ({ children }: { children: React.ReactNode }) => <div data-testid="modal-body">{children}</div>,
Footer: ({ children }: { children: React.ReactNode }) => <div data-testid="modal-footer">{children}</div>,
},
}));
beforeEach(() => {
vi.restoreAllMocks();
});
describe("QuickDraftModal — header", () => {
it("renders nothing when closed", () => {
render(
<QuickDraftModal
open={false}
onOpenChange={() => {}}
/>,
);
expect(screen.queryByTestId("modal")).toBeNull();
});
it("renders the Stock Order Reference input with a default month label", () => {
render(
<QuickDraftModal open onOpenChange={() => {}} />,
);
const input = screen.getByTestId("stock-order-reference");
expect(input).toBeInTheDocument();
// Value defaults to "Stock order entry — <MonthYear>" (e.g. "Stock order entry — Jul 2026").
expect((input as HTMLInputElement).value).toMatch(/^Stock order entry — /);
});
it("lets the buyer rename the reference", async () => {
const user = userEvent.setup();
render(
<QuickDraftModal open onOpenChange={() => {}} />,
);
const input = screen.getByTestId(
"stock-order-reference",
) as HTMLInputElement;
await user.clear(input);
await user.type(input, "Jul 2026 run");
expect(input.value).toBe("Jul 2026 run");
});
});

View file

@ -0,0 +1,66 @@
"use client";
import { useMemo } from "react";
import { Input } from "@/components/ui/input";
import { ModalShell } from "@/components/ui/modal-shell";
/**
* Quick-Draft Modal — item-first stock-order entry on /stock-orders.
*
* See docs/superpowers/specs/2026-07-30-stock-quick-draft-design.md
* for the full design. This file is the modal in its entirety; the
* three regions (search, cards, footer) live in sub-components
* further down so the test surface stays manageable.
*
* Data flow:
* - search & history hit /api/v1/stock-quick-draft/{search,supplier-history}
* - "Add & next" hits /api/v1/stock-quick-draft/add-line
* - cards region deletes via /api/v1/stock-quick-draft/line/[lineId]
*
* ModalShell in this codebase uses a prop-based pattern (eyebrow /
* title / description / children / footer) rather than exposing
* Header / Body / Footer sub-components, so the input lives as a
* child at the top of the dialog. The eyebrow surfaces the
* "Stock Order Reference" label above the input in production.
*/
export interface QuickDraftModalProps {
open: boolean;
onOpenChange: (next: boolean) => void;
}
function formatMonthYear(date: Date): string {
return date.toLocaleDateString("en-US", { month: "short", year: "numeric" });
}
export function QuickDraftModal({ open, onOpenChange }: QuickDraftModalProps) {
const defaultReference = useMemo(
() => `Stock order entry — ${formatMonthYear(new Date())}`,
// intentionally only computed once per mount
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
return (
<ModalShell.Dialog
open={open}
onOpenChange={onOpenChange}
size="xl"
eyebrow="Stock Order Reference"
title=""
footer={<div data-testid="region-footer" hidden />}
>
<div className="space-y-1.5">
<Input
data-testid="stock-order-reference"
defaultValue={defaultReference}
className="h-9 text-base font-semibold"
/>
</div>
{/* Regions wired up in later tasks */}
<div data-testid="region-search" hidden />
<div data-testid="region-cards" hidden />
</ModalShell.Dialog>
);
}