diff --git a/App/app/(portal)/approvals/[id]/manager-edit-po-form.tsx b/App/app/(portal)/approvals/[id]/manager-edit-po-form.tsx index e37fc46..749f6ac 100644 --- a/App/app/(portal)/approvals/[id]/manager-edit-po-form.tsx +++ b/App/app/(portal)/approvals/[id]/manager-edit-po-form.tsx @@ -10,6 +10,7 @@ import type { VesselOption, AccountGroup, CompanyOption } from "@/app/(portal)/p import { SearchableSelect } from "@/components/ui/searchable-select"; import { VendorSelect } from "@/components/ui/vendor-select"; import { DeliveryLocationField } from "@/components/po/delivery-location-field"; +import { ProjectCodeField } from "@/components/po/project-code-field"; import { PoTermsEditor } from "@/components/po/po-terms-editor"; import type { CatalogueCategory, PoTerm } from "@/lib/terms"; @@ -195,7 +196,7 @@ export function ManagerEditPoForm({ po, vessels, accounts, vendors, companies, d
- +
diff --git a/App/app/(portal)/po/[id]/edit/edit-po-form.tsx b/App/app/(portal)/po/[id]/edit/edit-po-form.tsx index 9d4a67f..27bb851 100644 --- a/App/app/(portal)/po/[id]/edit/edit-po-form.tsx +++ b/App/app/(portal)/po/[id]/edit/edit-po-form.tsx @@ -9,6 +9,7 @@ import { LineItemsEditor } from "@/components/po/po-line-items-editor"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { VendorSelect } from "@/components/ui/vendor-select"; import { DeliveryLocationField } from "@/components/po/delivery-location-field"; +import { ProjectCodeField } from "@/components/po/project-code-field"; import { PoTermsEditor } from "@/components/po/po-terms-editor"; import { UnsavedChangesGuard } from "@/components/po/unsaved-changes-guard"; import type { CatalogueCategory, PoTerm } from "@/lib/terms"; @@ -197,7 +198,7 @@ export function EditPoForm({ po, vessels, accounts, vendors, companies, delivery
- +
diff --git a/App/app/(portal)/po/new/new-po-form.tsx b/App/app/(portal)/po/new/new-po-form.tsx index 782275f..30aca25 100644 --- a/App/app/(portal)/po/new/new-po-form.tsx +++ b/App/app/(portal)/po/new/new-po-form.tsx @@ -9,6 +9,7 @@ import { FileUploader } from "@/components/po/file-uploader"; import { SearchableSelect } from "@/components/ui/searchable-select"; import { VendorSelect } from "@/components/ui/vendor-select"; import { DeliveryLocationField } from "@/components/po/delivery-location-field"; +import { ProjectCodeField } from "@/components/po/project-code-field"; import { PoTermsEditor } from "@/components/po/po-terms-editor"; import { UnsavedChangesGuard } from "@/components/po/unsaved-changes-guard"; import type { CatalogueCategory, PoTerm } from "@/lib/terms"; @@ -161,7 +162,7 @@ export function NewPoForm({ vessels, accounts, vendors, companies, deliveryOptio
- +
diff --git a/App/components/po/project-code-field.tsx b/App/components/po/project-code-field.tsx new file mode 100644 index 0000000..568617a --- /dev/null +++ b/App/components/po/project-code-field.tsx @@ -0,0 +1,34 @@ +/** + * Project Code dropdown (issue #124) — a native + + {currentMissing && } + {PROJECT_CODES.map((code) => ( + + ))} + + ); +} diff --git a/App/lib/validations/po.ts b/App/lib/validations/po.ts index 7be1042..312482c 100644 --- a/App/lib/validations/po.ts +++ b/App/lib/validations/po.ts @@ -18,6 +18,20 @@ export const TC_FIXED_LINE = export const TC_FIXED_LINE_2 = "We encourage bulk packaging and avoid plastic. No asbestos to be used in any product or packing material."; +/** + * Fixed list of selectable Project Codes (issue #124). The PO `projectCode` + * column stays a nullable free-text snapshot — this list only constrains how + * the value is picked in the three PO forms (so legacy / imported values are + * never rejected). Extend here to add a code everywhere at once. + */ +export const PROJECT_CODES = [ + "Petronet LNG Cochin", + "COMACOE Trombay", + "Haldia Reach", + "Haldia MMT", + "COMACOE Mandvi", +] as const; + export const TC_DEFAULTS = { tcDelivery: "Within 4 to 5 days", tcDispatch: "To be transported to site address as above. Freight Supplier's A/C", diff --git a/App/tests/unit/project-code-field.test.tsx b/App/tests/unit/project-code-field.test.tsx new file mode 100644 index 0000000..635e255 --- /dev/null +++ b/App/tests/unit/project-code-field.test.tsx @@ -0,0 +1,47 @@ +import { describe, it, expect } from "vitest"; +import { render, screen } from "@testing-library/react"; +import { ProjectCodeField } from "@/components/po/project-code-field"; +import { PROJECT_CODES } from "@/lib/validations/po"; + +function options(container: HTMLElement) { + return Array.from(container.querySelectorAll("option")).map((o) => ({ + value: o.getAttribute("value"), + text: o.textContent, + })); +} + +describe("ProjectCodeField", () => { + it("renders a select named projectCode with an empty option + every fixed code", () => { + const { container } = render(); + const select = container.querySelector("select"); + expect(select?.getAttribute("name")).toBe("projectCode"); + + const opts = options(container); + // empty "none" option first, then exactly the fixed codes + expect(opts[0].value).toBe(""); + expect(opts.slice(1).map((o) => o.value)).toEqual([...PROJECT_CODES]); + }); + + it("selects a current value that is one of the fixed codes (no duplicate option)", () => { + const { container } = render(); + const select = container.querySelector("select") as HTMLSelectElement; + expect(select.value).toBe("Haldia Reach"); + // only the fixed codes + empty option — no extra "(current)" entry + expect(container.querySelectorAll("option")).toHaveLength(PROJECT_CODES.length + 1); + }); + + it("preserves a legacy current value not in the list as a leading (current) option", () => { + const { container } = render(); + const select = container.querySelector("select") as HTMLSelectElement; + expect(select.value).toBe("Legacy Project X"); + expect(screen.getByText("Legacy Project X (current)")).toBeInTheDocument(); + // empty + (current) + fixed codes + expect(container.querySelectorAll("option")).toHaveLength(PROJECT_CODES.length + 2); + }); + + it("defaults to the empty option when no current value is given", () => { + const { container } = render(); + const select = container.querySelector("select") as HTMLSelectElement; + expect(select.value).toBe(""); + }); +});