feat(stock-quick-draft): modal skeleton + Stock Order Reference header
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
921ea4e63d
commit
2d4d0df6d1
2 changed files with 121 additions and 0 deletions
55
src/components/stock-orders/quick-draft-modal.test.tsx
Normal file
55
src/components/stock-orders/quick-draft-modal.test.tsx
Normal 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");
|
||||
});
|
||||
});
|
||||
66
src/components/stock-orders/quick-draft-modal.tsx
Normal file
66
src/components/stock-orders/quick-draft-modal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue