diff --git a/mechanic-pwa/frontend/src/components/repairs/WorksPickerModal.test.tsx b/mechanic-pwa/frontend/src/components/repairs/WorksPickerModal.test.tsx new file mode 100644 index 0000000..c5ed579 --- /dev/null +++ b/mechanic-pwa/frontend/src/components/repairs/WorksPickerModal.test.tsx @@ -0,0 +1,43 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { vi } from "vitest"; +import WorksPickerModal from "@/components/repairs/WorksPickerModal"; +import type { MechWork } from "@/api/repairsCreate"; + +const works: MechWork[] = [ + { id: 1, name: "Замена масла", category_id: 1, price: 1500, norma_hours: null }, + { id: 2, name: "Развал-схождение", category_id: 1, price: "2000", norma_hours: null }, +]; + +test("рендерит работы с ценой и зовёт onPick по клику", async () => { + const user = userEvent.setup(); + const onPick = vi.fn(); + render( + {}} onPick={onPick} onClose={() => {}} />, + ); + expect(screen.getByText("Замена масла")).toBeInTheDocument(); + expect(screen.getByText("1 500 ₽")).toBeInTheDocument(); // число из 1500 + expect(screen.getByText("2 000 ₽")).toBeInTheDocument(); // строка "2000" → число + await user.click(screen.getByText("Развал-схождение")); + expect(onPick).toHaveBeenCalledWith(works[1]); +}); + +test("пустой список → «Ничего не найдено»", () => { + render( + {}} onPick={() => {}} onClose={() => {}} />, + ); + expect(screen.getByText("Ничего не найдено")).toBeInTheDocument(); +}); + +test("ввод в поиск зовёт onQuery; кнопка назад — onClose", async () => { + const user = userEvent.setup(); + const onQuery = vi.fn(); + const onClose = vi.fn(); + render( + {}} onClose={onClose} />, + ); + await user.type(screen.getByPlaceholderText("Поиск работы…"), "масло"); + expect(onQuery).toHaveBeenCalled(); + await user.click(screen.getByLabelText("Закрыть")); + expect(onClose).toHaveBeenCalled(); +}); diff --git a/mechanic-pwa/frontend/src/components/repairs/WorksPickerModal.tsx b/mechanic-pwa/frontend/src/components/repairs/WorksPickerModal.tsx new file mode 100644 index 0000000..c547b3b --- /dev/null +++ b/mechanic-pwa/frontend/src/components/repairs/WorksPickerModal.tsx @@ -0,0 +1,74 @@ +/** + * Модалка выбора работы из каталога. Раньше была продублирована почти один-в-один + * в CreateRepairPage и EditRepairPage — вынесена в общий презентационный компонент. + * + * Состояние (открыта/запрос/результаты) живёт в родителе; здесь только UI + + * колбэки. Это сознательно: формы создания и правки ремонта разошлись по UX + * (inline-разблокировка цены против модалки-override, фото против их отсутствия), + * поэтому единая «форма ремонта» была бы хуже — общим является именно этот пикер. + */ +import { ChevronLeft } from "lucide-react"; +import { Input } from "@/components/ui/input"; +import { fmtMoney } from "@/lib/repairsFeed"; +import type { MechWork } from "@/api/repairsCreate"; + +interface Props { + query: string; + results: MechWork[]; + onQuery: (q: string) => void; + onPick: (work: MechWork) => void; + onClose: () => void; +} + +export default function WorksPickerModal({ query, results, onQuery, onPick, onClose }: Props) { + return ( +
+
+ + onQuery(e.target.value)} + /> +
+
+ {results.map((mw) => ( + + ))} + {results.length === 0 && ( +
+ Ничего не найдено +
+ )} +
+
+ ); +} diff --git a/mechanic-pwa/frontend/src/pages/repairs/CreateRepairPage.tsx b/mechanic-pwa/frontend/src/pages/repairs/CreateRepairPage.tsx index ec53f03..032daba 100644 --- a/mechanic-pwa/frontend/src/pages/repairs/CreateRepairPage.tsx +++ b/mechanic-pwa/frontend/src/pages/repairs/CreateRepairPage.tsx @@ -22,6 +22,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { repairCanSubmit, repairSubmitWhyDisabled } from "@/lib/repairForm"; +import WorksPickerModal from "@/components/repairs/WorksPickerModal"; import { createRepair, getCarContext, @@ -698,54 +699,15 @@ export default function CreateRepairPage() { - {/* Works picker modal */} + {/* Works picker modal — общий компонент (см. WorksPickerModal) */} {worksPicker.open && ( -
-
- - queryWorks(e.target.value)} - /> -
-
-
    - {worksPicker.results.map((w) => ( -
  • - -
  • - ))} - {worksPicker.results.length === 0 && ( -
  • - Нет работ по запросу -
  • - )} -
-
-
+ setWorksPicker({ open: false, q: "", results: [] })} + /> )} ); diff --git a/mechanic-pwa/frontend/src/pages/repairs/EditRepairPage.tsx b/mechanic-pwa/frontend/src/pages/repairs/EditRepairPage.tsx index d93a4b1..2a3ed55 100644 --- a/mechanic-pwa/frontend/src/pages/repairs/EditRepairPage.tsx +++ b/mechanic-pwa/frontend/src/pages/repairs/EditRepairPage.tsx @@ -17,6 +17,7 @@ import { type RepairDetailResponse, } from "@/api/repairsFeed"; import { searchWorks, suggestParts, type MechWork } from "@/api/repairsCreate"; +import WorksPickerModal from "@/components/repairs/WorksPickerModal"; interface WorkRow { rowKey: string; @@ -376,41 +377,15 @@ export default function EditRepairPage() { )} - {/* Works picker modal */} + {/* Works picker modal — общий компонент (см. WorksPickerModal) */} {worksPicker.open && ( -
-
- - queryWorks(e.target.value)} - /> -
-
- {worksPicker.results.map((mw) => ( - - ))} - {worksPicker.results.length === 0 && ( -
- Ничего не найдено -
- )} -
-
+ setWorksPicker({ open: false, q: "", results: [] })} + /> )} {/* Override modal */}