fix(dashboard): Fix minor issues with Reservations and Inventroy Create forms (#8657)

This commit is contained in:
Kasper Fabricius Kristensen
2024-08-20 13:31:31 +02:00
committed by GitHub
parent 45f9961d19
commit cfdd056d70
17 changed files with 289 additions and 281 deletions
@@ -1,6 +1,6 @@
import { castNumber } from "./cast-number" import { castNumber } from "./cast-number"
export function parseOptionalFormValue<T>( export function transformNullableFormValue<T>(
value: T, value: T,
nullify = true nullify = true
): T | undefined | null { ): T | undefined | null {
@@ -16,20 +16,21 @@ export function parseOptionalFormValue<T>(
} }
type Nullable<T> = { [K in keyof T]: T[K] | null } type Nullable<T> = { [K in keyof T]: T[K] | null }
type Optional<T> = { [K in keyof T]: T[K] | undefined }
export function parseOptionalFormData<T extends Record<string, unknown>>( export function transformNullableFormData<
data: T, T extends Record<string, unknown>,
nullify = true K extends boolean = true
): Nullable<T> { >(data: T, nullify: K = true as K): K extends true ? Nullable<T> : Optional<T> {
return Object.entries(data).reduce((acc, [key, value]) => { return Object.entries(data).reduce((acc, [key, value]) => {
return { return {
...acc, ...acc,
[key]: parseOptionalFormValue(value, nullify), [key]: transformNullableFormValue(value, nullify),
} }
}, {} as Nullable<T>) }, {} as K extends true ? Nullable<T> : Optional<T>)
} }
export function parseOptionalFormNumber( export function transformNullableFormNumber(
value?: string | number, value?: string | number,
nullify = true nullify = true
) { ) {
@@ -46,3 +47,21 @@ export function parseOptionalFormNumber(
return value return value
} }
type NullableNumbers = Record<string, number | null>
type OptionalNumbers = Record<string, number | undefined>
export function transformNullableFormNumbers<
T extends Record<string, string | number | undefined>,
K extends boolean = true
>(
data: T,
nullify: K = true as K
): K extends true ? NullableNumbers : OptionalNumbers {
return Object.entries(data).reduce((acc, [key, value]) => {
return {
...acc,
[key]: transformNullableFormNumber(value, nullify),
}
}, {} as K extends true ? NullableNumbers : OptionalNumbers)
}
@@ -546,9 +546,7 @@ export const RouteMap: RouteObject[] = [
{ {
path: "create", path: "create",
lazy: () => lazy: () =>
import( import("../../routes/reservations/reservation-create"),
"../../routes/reservations/reservation-list/create-reservation"
),
}, },
], ],
}, },
@@ -1 +0,0 @@
export * from "./create-inventory-item-form.tsx"
@@ -0,0 +1 @@
export * from "./inventory-create-form.tsx"
@@ -1,18 +1,23 @@
import { useMemo } from "react"
import { HttpTypes } from "@medusajs/types" import { HttpTypes } from "@medusajs/types"
import { useMemo } from "react"
import { UseFormReturn } from "react-hook-form" import { UseFormReturn } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { DataGrid } from "../../../../../components/data-grid"
import { createDataGridHelper } from "../../../../../components/data-grid/utils" import {
DataGrid,
createDataGridHelper,
} from "../../../../../components/data-grid"
import { useRouteModal } from "../../../../../components/modals" import { useRouteModal } from "../../../../../components/modals"
import { useStockLocations } from "../../../../../hooks/api/stock-locations" import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { CreateInventoryItemSchema } from "./schema"
type Props = { type InventoryAvailabilityFormProps = {
form: UseFormReturn<{}> form: UseFormReturn<CreateInventoryItemSchema>
} }
export const CreateInventoryAvailabilityForm = ({ form }: Props) => { export const InventoryAvailabilityForm = ({
form,
}: InventoryAvailabilityFormProps) => {
const { isPending, stock_locations = [] } = useStockLocations({ limit: 999 }) const { isPending, stock_locations = [] } = useStockLocations({ limit: 999 })
const { setCloseOnEscape } = useRouteModal() const { setCloseOnEscape } = useRouteModal()
@@ -1,22 +1,21 @@
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import React, { useEffect } from "react"
import { useForm } from "react-hook-form"
import * as zod from "zod"
import { import {
Button, Button,
Heading, Heading,
Input, Input,
ProgressStatus, ProgressStatus,
ProgressTabs, ProgressTabs,
Switch,
Textarea, Textarea,
clx, clx,
toast, toast,
} from "@medusajs/ui" } from "@medusajs/ui"
import { useCallback, useEffect, useState } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Divider } from "../../../../../components/common/divider"
import { Form } from "../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { SwitchBox } from "../../../../../components/common/switch-box"
import { CountrySelect } from "../../../../../components/inputs/country-select" import { CountrySelect } from "../../../../../components/inputs/country-select"
import { import {
RouteFocusModal, RouteFocusModal,
@@ -27,9 +26,13 @@ import {
useCreateInventoryItem, useCreateInventoryItem,
} from "../../../../../hooks/api/inventory" } from "../../../../../hooks/api/inventory"
import { sdk } from "../../../../../lib/client" import { sdk } from "../../../../../lib/client"
import {
transformNullableFormData,
transformNullableFormNumbers,
} from "../../../../../lib/form-helpers"
import { queryClient } from "../../../../../lib/query-client" import { queryClient } from "../../../../../lib/query-client"
import { optionalInt } from "../../../../../lib/validation" import { InventoryAvailabilityForm } from "./inventory-availability-form"
import { CreateInventoryAvailabilityForm } from "./create-inventory-availability-form" import { CreateInventoryItemSchema } from "./schema"
enum Tab { enum Tab {
DETAILS = "details", DETAILS = "details",
@@ -40,31 +43,12 @@ type StepStatus = {
[key in Tab]: ProgressStatus [key in Tab]: ProgressStatus
} }
const CreateInventoryItemSchema = zod.object({ export function InventoryCreateForm() {
title: zod.string().min(1),
sku: zod.string().optional(),
hs_code: zod.string().optional(),
weight: optionalInt,
length: optionalInt,
height: optionalInt,
width: optionalInt,
origin_country: zod.string().optional(),
mid_code: zod.string().optional(),
material: zod.string().optional(),
description: zod.string().optional(),
requires_shipping: zod.boolean().optional(),
thumbnail: zod.string().optional(),
locations: zod.record(zod.string(), zod.number().optional()).optional(),
// metadata: zod.record(zod.string(), zod.unknown()).optional(),
})
export function CreateInventoryItemForm() {
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const [tab, setTab] = React.useState<Tab>(Tab.DETAILS) const [tab, setTab] = useState<Tab>(Tab.DETAILS)
const form = useForm<zod.infer<typeof CreateInventoryItemSchema>>({ const form = useForm<CreateInventoryItemSchema>({
defaultValues: { defaultValues: {
title: "", title: "",
sku: "", sku: "",
@@ -83,66 +67,86 @@ export function CreateInventoryItemForm() {
resolver: zodResolver(CreateInventoryItemSchema), resolver: zodResolver(CreateInventoryItemSchema),
}) })
const {
trigger,
formState: { isDirty },
} = form
const { mutateAsync: createInventoryItem, isPending: isLoading } = const { mutateAsync: createInventoryItem, isPending: isLoading } =
useCreateInventoryItem() useCreateInventoryItem()
const handleSubmit = form.handleSubmit(async (data) => { const handleSubmit = form.handleSubmit(async (data) => {
const { locations, ...payload } = data const { locations, weight, length, height, width, ...payload } = data
for (const k in payload) { const cleanData = transformNullableFormData(payload, false)
if (payload[k] === "") { const cleanNumbers = transformNullableFormNumbers(
delete payload[k] {
continue weight,
length,
height,
width,
},
false
)
const { inventory_item } = await createInventoryItem(
{
...cleanData,
...cleanNumbers,
},
{
onError: (e) => {
toast.error(e.message)
return
},
} }
)
if (["weight", "length", "height", "width"].includes(k)) { await sdk.admin.inventoryItem
payload[k] = parseInt(payload[k]) .batchUpdateLevels(inventory_item.id, {
} create: Object.entries(locations ?? {})
} .filter(([_, quantiy]) => !!quantiy)
.map(([location_id, stocked_quantity]) => ({
try { location_id,
const { inventory_item } = await createInventoryItem(payload) stocked_quantity,
})),
try { })
await sdk.admin.inventoryItem.batchUpdateLevels(inventory_item.id, { .then(async () => {
create: Object.entries(locations)
.filter(([_, quantiy]) => !!quantiy)
.map(([location_id, stocked_quantity]) => ({
location_id,
stocked_quantity,
})),
})
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: inventoryItemsQueryKeys.lists(), queryKey: inventoryItemsQueryKeys.lists(),
}) })
} catch (e) { })
.catch((e) => {
// Since the inventory item is created, we only log the error,
// but still close the modal to prevent the user from trying to
// create the same item again.
toast.error(e.message) toast.error(e.message)
} })
.finally(() => {
handleSuccess() handleSuccess()
} catch (e) { })
toast.error(e.message)
}
}) })
const [status, setStatus] = React.useState<StepStatus>({ const [status, setStatus] = useState<StepStatus>({
[Tab.AVAILABILITY]: "not-started", [Tab.AVAILABILITY]: "not-started",
[Tab.DETAILS]: "not-started", [Tab.DETAILS]: "not-started",
}) })
const onTabChange = React.useCallback(async (value: Tab) => { const onTabChange = useCallback(
const result = await form.trigger() async (value: Tab) => {
const result = await trigger()
if (!result) { if (!result) {
return return
} }
setTab(value) setTab(value)
}, []) },
[trigger]
)
const onNext = React.useCallback(async () => { const onNext = useCallback(async () => {
const result = await form.trigger() const result = await trigger()
if (!result) { if (!result) {
return return
@@ -156,18 +160,18 @@ export function CreateInventoryItemForm() {
case Tab.AVAILABILITY: case Tab.AVAILABILITY:
break break
} }
}, [tab]) }, [tab, trigger])
useEffect(() => { useEffect(() => {
if (form.formState.isDirty) { if (isDirty) {
setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "in-progress" })) setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "in-progress" }))
} else { } else {
setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "not-started" })) setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "not-started" }))
} }
}, [form.formState.isDirty]) }, [isDirty])
useEffect(() => { useEffect(() => {
if (tab === Tab.DETAILS && form.formState.isDirty) { if (tab === Tab.DETAILS && isDirty) {
setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "in-progress" })) setStatus((prev) => ({ ...prev, [Tab.DETAILS]: "in-progress" }))
} }
@@ -178,18 +182,18 @@ export function CreateInventoryItemForm() {
[Tab.AVAILABILITY]: "in-progress", [Tab.AVAILABILITY]: "in-progress",
})) }))
} }
}, [tab]) }, [tab, isDirty])
return ( return (
<RouteFocusModal.Form form={form}> <RouteFocusModal.Form form={form}>
<form <ProgressTabs
className="flex h-full flex-col overflow-hidden" value={tab}
onSubmit={handleSubmit} className="h-full"
onValueChange={(tab) => onTabChange(tab as Tab)}
> >
<ProgressTabs <form
value={tab} className="flex h-full flex-col overflow-hidden"
className="h-full" onSubmit={handleSubmit}
onValueChange={(tab) => onTabChange(tab as Tab)}
> >
<RouteFocusModal.Header> <RouteFocusModal.Header>
<ProgressTabs.List className="border-ui-border-base -my-2 ml-2 min-w-0 flex-1 border-l"> <ProgressTabs.List className="border-ui-border-base -my-2 ml-2 min-w-0 flex-1 border-l">
@@ -212,123 +216,52 @@ export function CreateInventoryItemForm() {
</span> </span>
</ProgressTabs.Trigger> </ProgressTabs.Trigger>
</ProgressTabs.List> </ProgressTabs.List>
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button variant="secondary" size="small">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
size="small"
className="whitespace-nowrap"
isLoading={isLoading}
onClick={tab !== Tab.AVAILABILITY ? onNext : undefined}
key={tab === Tab.AVAILABILITY ? "details" : "pricing"}
type={tab === Tab.AVAILABILITY ? "submit" : "button"}
>
{tab === Tab.AVAILABILITY
? t("actions.save")
: t("general.next")}
</Button>
</div>
</RouteFocusModal.Header> </RouteFocusModal.Header>
<RouteFocusModal.Body <RouteFocusModal.Body
className={clx( className={clx(
"flex h-full w-full flex-col items-center divide-y overflow-hidden", "flex h-full w-full flex-col items-center divide-y overflow-hidden px-3",
{ "mx-auto": tab === Tab.DETAILS } { "mx-auto": tab === Tab.DETAILS }
)} )}
> >
<ProgressTabs.Content value={Tab.DETAILS} className="h-full w-full"> <ProgressTabs.Content
<div className="mx-auto w-[720px] px-1 py-8"> value={Tab.DETAILS}
<Heading level="h2" className="mb-12 mt-8 text-2xl"> className="h-full w-full overflow-auto"
{t("inventory.create.title")} >
</Heading> <div className="mx-auto flex w-full max-w-[720px] flex-col gap-y-8 px-px py-16">
<div className="flex flex-col gap-y-8">
<div className="flex flex-col gap-y-6"> <Heading>{t("inventory.create.title")}</Heading>
<div className="grid grid-cols-1 gap-x-3 gap-y-6 lg:grid-cols-2"> <div className="flex flex-col gap-y-6">
<Form.Field <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
control={form.control}
name="title"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("fields.title")}</Form.Label>
<Form.Control>
<Input
{...field}
placeholder={t("fields.title")}
/>
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<Form.Field
control={form.control}
name="sku"
render={({ field }) => {
return (
<Form.Item>
<Form.Label>{t("fields.sku")}</Form.Label>
<Form.Control>
<Input {...field} placeholder="sku-123" />
</Form.Control>
<Form.ErrorMessage />
</Form.Item>
)
}}
/>
<div className="col-span-2">
<Form.Field <Form.Field
control={form.control} control={form.control}
name="description" name="title"
render={({ field }) => { render={({ field }) => {
return ( return (
<Form.Item> <Form.Item>
<Form.Label optional> <Form.Label>{t("fields.title")}</Form.Label>
{t("products.fields.description.label")}
</Form.Label>
<Form.Control> <Form.Control>
<Textarea <Input
{...field} {...field}
placeholder="The item description" placeholder={t("fields.title")}
/> />
</Form.Control> </Form.Control>
<Form.ErrorMessage />
</Form.Item> </Form.Item>
) )
}} }}
/> />
</div>
<div className="col-span-2">
<Form.Field <Form.Field
control={form.control} control={form.control}
name="requires_shipping" name="sku"
render={({ field: { value, onChange, ...field } }) => { render={({ field }) => {
return ( return (
<Form.Item> <Form.Item>
<div className="flex flex-col gap-y-1"> <Form.Label>{t("fields.sku")}</Form.Label>
<div className="flex items-center justify-between"> <Form.Control>
<Form.Label> <Input {...field} placeholder="sku-123" />
{t("inventory.create.requiresShipping")} </Form.Control>
</Form.Label>
<Form.Control>
<Switch
checked={value}
onCheckedChange={(checked) =>
onChange(!!checked)
}
{...field}
/>
</Form.Control>
</div>
<Form.Hint>
{t("inventory.create.requiresShippingHint")}
</Form.Hint>
</div>
<Form.ErrorMessage /> <Form.ErrorMessage />
</Form.Item> </Form.Item>
) )
@@ -336,40 +269,43 @@ export function CreateInventoryItemForm() {
/> />
</div> </div>
{/* <Form.Field*/} <Form.Field
{/* className="col-span-1"*/} control={form.control}
{/* control={form.control}*/} name="description"
{/* name="location_ids"*/} render={({ field }) => {
{/* render={({ field }) => {*/} return (
{/* return (*/} <Form.Item>
{/* <Form.Item>*/} <Form.Label optional>
{/* <Form.Label optional>*/} {t("products.fields.description.label")}
{/* {t("inventory.create.locations")}*/} </Form.Label>
{/* </Form.Label>*/} <Form.Control>
{/* <Form.Control>*/} <Textarea
{/* <Combobox*/} {...field}
{/* {...field}*/} placeholder="The item description"
{/* multiple*/} />
{/* options={locations.options}*/} </Form.Control>
{/* searchValue={locations.searchValue}*/} </Form.Item>
{/* onSearchValueChange={*/} )
{/* locations.onSearchValueChange*/} }}
{/* }*/} />
{/* fetchNextPage={locations.fetchNextPage}*/}
{/* />*/}
{/* </Form.Control>*/}
{/* <Form.ErrorMessage />*/}
{/* </Form.Item>*/}
{/* )*/}
{/* }}*/}
{/* />*/}
</div> </div>
<Heading level="h3" className="my-6"> <SwitchBox
control={form.control}
name="requires_shipping"
label={t("inventory.create.requiresShipping")}
description={t("inventory.create.requiresShippingHint")}
/>
</div>
<Divider />
<div className="flex flex-col gap-y-6">
<Heading level="h2">
{t("inventory.create.attributes")} {t("inventory.create.attributes")}
</Heading> </Heading>
<div className="grid grid-cols-1 gap-x-4 gap-y-8 lg:grid-cols-2"> <div className="grid grid-cols-1 gap-x-4 gap-y-4 lg:grid-cols-2 lg:gap-y-8">
<Form.Field <Form.Field
control={form.control} control={form.control}
name="width" name="width"
@@ -534,11 +470,32 @@ export function CreateInventoryItemForm() {
value={Tab.AVAILABILITY} value={Tab.AVAILABILITY}
className="size-full" className="size-full"
> >
<CreateInventoryAvailabilityForm form={form} /> <InventoryAvailabilityForm form={form} />
</ProgressTabs.Content> </ProgressTabs.Content>
</RouteFocusModal.Body> </RouteFocusModal.Body>
</ProgressTabs> <RouteFocusModal.Footer>
</form> <div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button variant="secondary" size="small">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
size="small"
className="whitespace-nowrap"
isLoading={isLoading}
onClick={tab !== Tab.AVAILABILITY ? onNext : undefined}
key={tab === Tab.AVAILABILITY ? "details" : "pricing"}
type={tab === Tab.AVAILABILITY ? "submit" : "button"}
>
{tab === Tab.AVAILABILITY
? t("actions.save")
: t("general.next")}
</Button>
</div>
</RouteFocusModal.Footer>
</form>
</ProgressTabs>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )
} }
@@ -0,0 +1,23 @@
import { z } from "zod"
import { optionalInt } from "../../../../../lib/validation"
export const CreateInventoryItemSchema = z.object({
title: z.string().min(1),
description: z.string().optional(),
sku: z.string().optional(),
hs_code: z.string().optional(),
weight: optionalInt,
length: optionalInt,
height: optionalInt,
width: optionalInt,
origin_country: z.string().optional(),
mid_code: z.string().optional(),
material: z.string().optional(),
requires_shipping: z.boolean().optional(),
thumbnail: z.string().optional(),
locations: z.record(z.string(), z.number().optional()).optional(),
})
export type CreateInventoryItemSchema = z.infer<
typeof CreateInventoryItemSchema
>
@@ -1,10 +1,10 @@
import { RouteFocusModal } from "../../../components/modals" import { RouteFocusModal } from "../../../components/modals"
import { CreateInventoryItemForm } from "./components/create-inventory-item-form" import { InventoryCreateForm } from "./components/inventory-create-form"
export function InventoryCreate() { export function InventoryCreate() {
return ( return (
<RouteFocusModal> <RouteFocusModal>
<CreateInventoryItemForm /> <InventoryCreateForm />
</RouteFocusModal> </RouteFocusModal>
) )
} }
@@ -15,8 +15,8 @@ import {
} from "../../../../../components/modals" } from "../../../../../components/modals"
import { useUpdateProductVariant } from "../../../../../hooks/api/products" import { useUpdateProductVariant } from "../../../../../hooks/api/products"
import { import {
parseOptionalFormData, transformNullableFormData,
parseOptionalFormNumber, transformNullableFormNumber,
} from "../../../../../lib/form-helpers" } from "../../../../../lib/form-helpers"
import { optionalInt } from "../../../../../lib/validation" import { optionalInt } from "../../../../../lib/validation"
@@ -98,15 +98,15 @@ export const ProductEditVariantForm = ({
...optional ...optional
} = data } = data
const nullableData = parseOptionalFormData(optional) const nullableData = transformNullableFormData(optional)
await mutateAsync( await mutateAsync(
{ {
id: variant.id, id: variant.id,
weight: parseOptionalFormNumber(weight), weight: transformNullableFormNumber(weight),
height: parseOptionalFormNumber(height), height: transformNullableFormNumber(height),
width: parseOptionalFormNumber(width), width: transformNullableFormNumber(width),
length: parseOptionalFormNumber(length), length: transformNullableFormNumber(length),
title, title,
allow_backorder, allow_backorder,
manage_inventory, manage_inventory,
@@ -12,7 +12,7 @@ import {
useRouteModal, useRouteModal,
} from "../../../../../components/modals" } from "../../../../../components/modals"
import { useUpdateProduct } from "../../../../../hooks/api/products" import { useUpdateProduct } from "../../../../../hooks/api/products"
import { parseOptionalFormData } from "../../../../../lib/form-helpers" import { transformNullableFormData } from "../../../../../lib/form-helpers"
type EditProductFormProps = { type EditProductFormProps = {
product: HttpTypes.AdminProduct product: HttpTypes.AdminProduct
@@ -50,7 +50,7 @@ export const EditProductForm = ({ product }: EditProductFormProps) => {
const handleSubmit = form.handleSubmit(async (data) => { const handleSubmit = form.handleSubmit(async (data) => {
const { title, discountable, handle, status, ...optional } = data const { title, discountable, handle, status, ...optional } = data
const nullableData = parseOptionalFormData(optional) const nullableData = transformNullableFormData(optional)
await mutateAsync( await mutateAsync(
{ {
@@ -0,0 +1 @@
export * from "./reservation-create-from"
@@ -4,19 +4,19 @@ import { Button, Heading, Input, Text, Textarea, toast } from "@medusajs/ui"
import { import {
RouteFocusModal, RouteFocusModal,
useRouteModal, useRouteModal,
} from "../../../../../../components/modals" } from "../../../../../components/modals"
import { zodResolver } from "@hookform/resolvers/zod" import { zodResolver } from "@hookform/resolvers/zod"
import { InventoryTypes } from "@medusajs/types" import { InventoryTypes } from "@medusajs/types"
import React from "react" import React from "react"
import { useForm } from "react-hook-form" import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next" import { useTranslation } from "react-i18next"
import { Form } from "../../../../../../components/common/form" import { Form } from "../../../../../components/common/form"
import { Combobox } from "../../../../../../components/inputs/combobox" import { Combobox } from "../../../../../components/inputs/combobox"
import { useInventoryItems } from "../../../../../../hooks/api/inventory" import { useInventoryItems } from "../../../../../hooks/api/inventory"
import { useCreateReservationItem } from "../../../../../../hooks/api/reservations" import { useCreateReservationItem } from "../../../../../hooks/api/reservations"
import { useStockLocations } from "../../../../../../hooks/api/stock-locations" import { useStockLocations } from "../../../../../hooks/api/stock-locations"
import { InventoryItemRes } from "../../../../../../types/api-responses" import { InventoryItemRes } from "../../../../../types/api-responses"
export const CreateReservationSchema = zod.object({ export const CreateReservationSchema = zod.object({
inventory_item_id: zod.string().min(1), inventory_item_id: zod.string().min(1),
@@ -44,7 +44,7 @@ const AttributeGridRow = ({
) )
} }
export const CreateReservationForm = (props: { inventoryItemId?: string }) => { export const ReservationCreateForm = (props: { inventoryItemId?: string }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { handleSuccess } = useRouteModal() const { handleSuccess } = useRouteModal()
const [inventorySearch, setInventorySearch] = React.useState<string | null>( const [inventorySearch, setInventorySearch] = React.useState<string | null>(
@@ -109,25 +109,12 @@ export const CreateReservationForm = (props: { inventoryItemId?: string }) => {
return ( return (
<RouteFocusModal.Form form={form}> <RouteFocusModal.Form form={form}>
<form onSubmit={handleSubmit}> <form
<RouteFocusModal.Header> onSubmit={handleSubmit}
<div className="flex items-center justify-end gap-x-2"> className="flex size-full flex-col overflow-hidden"
<RouteFocusModal.Close asChild> >
<Button variant="secondary" size="small"> <RouteFocusModal.Header />
{t("actions.cancel")} <RouteFocusModal.Body className="flex flex-1 flex-col items-center overflow-auto py-16">
</Button>
</RouteFocusModal.Close>
<Button
type="submit"
variant="primary"
size="small"
isLoading={isPending}
>
{t("actions.create")}
</Button>
</div>
</RouteFocusModal.Header>
<RouteFocusModal.Body className="flex flex-col items-center pt-[72px]">
<div className="flex w-full max-w-[720px] flex-col gap-y-8"> <div className="flex w-full max-w-[720px] flex-col gap-y-8">
<Heading>{t("inventory.reservation.create")}</Heading> <Heading>{t("inventory.reservation.create")}</Heading>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
@@ -193,7 +180,7 @@ export const CreateReservationForm = (props: { inventoryItemId?: string }) => {
}} }}
/> />
</div> </div>
<div className="text-ui-fg-subtle shadow-elevation-card-rest grid grid-rows-4 divide-y rounded-lg border"> <div className="text-ui-fg-subtle shadow-elevation-card-rest grid grid-rows-4 divide-y rounded-lg">
<AttributeGridRow <AttributeGridRow
title={t("fields.title")} title={t("fields.title")}
value={ value={
@@ -282,6 +269,23 @@ export const CreateReservationForm = (props: { inventoryItemId?: string }) => {
/> />
</div> </div>
</RouteFocusModal.Body> </RouteFocusModal.Body>
<RouteFocusModal.Footer>
<div className="flex items-center justify-end gap-x-2">
<RouteFocusModal.Close asChild>
<Button variant="secondary" size="small">
{t("actions.cancel")}
</Button>
</RouteFocusModal.Close>
<Button
type="submit"
variant="primary"
size="small"
isLoading={isPending}
>
{t("actions.create")}
</Button>
</div>
</RouteFocusModal.Footer>
</form> </form>
</RouteFocusModal.Form> </RouteFocusModal.Form>
) )
@@ -0,0 +1,2 @@
export { ReservationCreate as Component } from "./reservation-create";
@@ -0,0 +1,16 @@
import { useSearchParams } from "react-router-dom"
import { RouteFocusModal } from "../../../components/modals"
import { ReservationCreateForm } from "./components/reservation-create-from"
export const ReservationCreate = () => {
const [params] = useSearchParams()
const inventoryItemId = params.get("item_id")
return (
<RouteFocusModal>
<ReservationCreateForm inventoryItemId={inventoryItemId} />
</RouteFocusModal>
)
}
@@ -1,15 +0,0 @@
import { useSearchParams } from "react-router-dom"
import { RouteFocusModal } from "../../../../components/modals"
import { CreateReservationForm } from "./components/create-reservation-form"
export const CreateReservationModal = () => {
const [params] = useSearchParams()
const inventoryItemId = params.get("item_id")
return (
<RouteFocusModal>
<CreateReservationForm inventoryItemId={inventoryItemId} />
</RouteFocusModal>
)
}
@@ -1 +0,0 @@
export { CreateReservationModal as Component } from "./create-reservation-modal"