fix(dashboard): location UI fixes (#7288)
* fix: a few UI fixes * fix: domain name * feat: edit service zone areas * feat: edit shipping option prices * fix: sorting
This commit is contained in:
1 parent
a775d57255
commit
694434d51a
18 files changed
+803
-22
No files matched your search
@@ -62,6 +62,7 @@
|
|||||||
},
|
},
|
||||||
"actions": {
|
"actions": {
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"select": "Select",
|
||||||
"saveAsDraft": "Save as draft",
|
"saveAsDraft": "Save as draft",
|
||||||
"publish": "Publish",
|
"publish": "Publish",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
@@ -653,8 +654,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"shipping": {
|
"shipping": {
|
||||||
"title": "Location & Shipping",
|
"title": "Locations & Shipping",
|
||||||
"domain": "Location & Shipping",
|
"domain": "Locations & Shipping",
|
||||||
"description": "Choose where you ship and how much you charge for shipping at checkout. Define shipping options specific for your locations.",
|
"description": "Choose where you ship and how much you charge for shipping at checkout. Define shipping options specific for your locations.",
|
||||||
"createLocation": "Create location",
|
"createLocation": "Create location",
|
||||||
"createLocationDetailsHint": "Specify the details of the location.",
|
"createLocationDetailsHint": "Specify the details of the location.",
|
||||||
@@ -714,10 +715,12 @@
|
|||||||
"edit": {
|
"edit": {
|
||||||
"title": "Edit Service Zone"
|
"title": "Edit Service Zone"
|
||||||
},
|
},
|
||||||
|
"editAreasTitle": "Manage {{zone}} areas",
|
||||||
"deleteWarning": "Are you sure you want to delete \"{{name}}\". This will also delete all assocciated shipping options.",
|
"deleteWarning": "Are you sure you want to delete \"{{name}}\". This will also delete all assocciated shipping options.",
|
||||||
"toast": {
|
"toast": {
|
||||||
"delete": "Zone \"{{name}}\" deleted successfully."
|
"delete": "Zone \"{{name}}\" deleted successfully."
|
||||||
},
|
},
|
||||||
|
"manageAreas": "Manage areas",
|
||||||
"editPrices": "Edit prices",
|
"editPrices": "Edit prices",
|
||||||
"editOption": "Edit option",
|
"editOption": "Edit option",
|
||||||
"optionsLength_one": "shipping option",
|
"optionsLength_one": "shipping option",
|
||||||
@@ -745,8 +748,8 @@
|
|||||||
"allocation": "Shipping amount",
|
"allocation": "Shipping amount",
|
||||||
"fixed": "Fixed",
|
"fixed": "Fixed",
|
||||||
"fixedDescription": "Shipping option's price is always the same amount.",
|
"fixedDescription": "Shipping option's price is always the same amount.",
|
||||||
"enable": "Enable in store",
|
"enable": "Show publicly",
|
||||||
"enableDescription": "Enable or disable the shipping option visiblity in store",
|
"enableDescription": "When disabled, the shipping option can only be applied by admins.",
|
||||||
"calculated": "Calculated",
|
"calculated": "Calculated",
|
||||||
"calculatedDescription": "Shipping option's price is calculated by the fulfillment provider.",
|
"calculatedDescription": "Shipping option's price is calculated by the fulfillment provider.",
|
||||||
"profile": "Shipping profile"
|
"profile": "Shipping profile"
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export const useStockLocation = (
|
|||||||
) => {
|
) => {
|
||||||
const { data, ...rest } = useQuery({
|
const { data, ...rest } = useQuery({
|
||||||
queryFn: () => client.stockLocations.retrieve(id, query),
|
queryFn: () => client.stockLocations.retrieve(id, query),
|
||||||
queryKey: stockLocationsQueryKeys.detail(id, query),
|
queryKey: stockLocationsQueryKeys.details(),
|
||||||
...options,
|
...options,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -744,6 +744,13 @@ export const RouteMap: RouteObject[] = [
|
|||||||
"../../v2-routes/shipping/service-zone-edit"
|
"../../v2-routes/shipping/service-zone-edit"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "edit-areas",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../v2-routes/shipping/service-zone-areas-edit"
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "shipping-option",
|
path: "shipping-option",
|
||||||
children: [
|
children: [
|
||||||
@@ -764,6 +771,13 @@ export const RouteMap: RouteObject[] = [
|
|||||||
"../../v2-routes/shipping/shipping-option-edit"
|
"../../v2-routes/shipping/shipping-option-edit"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "edit-pricing",
|
||||||
|
lazy: () =>
|
||||||
|
import(
|
||||||
|
"../../v2-routes/shipping/shipping-options-edit-pricing"
|
||||||
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+9
-8
@@ -156,7 +156,7 @@ function ShippingOption({
|
|||||||
{
|
{
|
||||||
label: t("shipping.serviceZone.editPrices"),
|
label: t("shipping.serviceZone.editPrices"),
|
||||||
icon: <CurrencyDollar />,
|
icon: <CurrencyDollar />,
|
||||||
disabled: true,
|
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${option.service_zone_id}/shipping-option/${option.id}/edit-pricing`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: t("actions.delete"),
|
label: t("actions.delete"),
|
||||||
@@ -199,7 +199,7 @@ function ServiceZoneOptions({
|
|||||||
{t("shipping.serviceZone.shippingOptions")}
|
{t("shipping.serviceZone.shippingOptions")}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
className="text-ui-fg-interactive txt-small px-0 font-medium hover:bg-transparent"
|
className="text-ui-fg-interactive txt-small px-0 font-medium hover:bg-transparent active:bg-transparent"
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(
|
navigate(
|
||||||
@@ -231,7 +231,7 @@ function ServiceZoneOptions({
|
|||||||
{t("shipping.serviceZone.returnOptions")}
|
{t("shipping.serviceZone.returnOptions")}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
className="text-ui-fg-interactive txt-small px-0 font-medium hover:bg-transparent"
|
className="text-ui-fg-interactive txt-small px-0 font-medium hover:bg-transparent active:bg-transparent"
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
navigate(
|
navigate(
|
||||||
@@ -313,6 +313,7 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
|
|||||||
.filter((g) => g.type === "country")
|
.filter((g) => g.type === "country")
|
||||||
.map((g) => g.country_code)
|
.map((g) => g.country_code)
|
||||||
.map((code) => staticCountries.find((c) => c.iso_2 === code))
|
.map((code) => staticCountries.find((c) => c.iso_2 === code))
|
||||||
|
.sort((c1, c2) => c1.name.localeCompare(c2.name))
|
||||||
}, zone.geo_zones)
|
}, zone.geo_zones)
|
||||||
|
|
||||||
const [shippingOptionsCount, returnOptionsCount] = useMemo(() => {
|
const [shippingOptionsCount, returnOptionsCount] = useMemo(() => {
|
||||||
@@ -385,16 +386,16 @@ function ServiceZone({ zone, locationId, fulfillmentSetId }: ServiceZoneProps) {
|
|||||||
groups={[
|
groups={[
|
||||||
{
|
{
|
||||||
actions: [
|
actions: [
|
||||||
// {
|
|
||||||
// label: t("shipping.serviceZone.addOption"),
|
|
||||||
// icon: <Plus />,
|
|
||||||
// to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/shipping-option/create`,
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
label: t("actions.edit"),
|
label: t("actions.edit"),
|
||||||
icon: <PencilSquare />,
|
icon: <PencilSquare />,
|
||||||
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit`,
|
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t("shipping.serviceZone.manageAreas"),
|
||||||
|
icon: <Map />,
|
||||||
|
to: `/settings/shipping/${locationId}/fulfillment-set/${fulfillmentSetId}/service-zone/${zone.id}/edit-areas`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t("actions.delete"),
|
label: t("actions.delete"),
|
||||||
icon: <Trash />,
|
icon: <Trash />,
|
||||||
|
|||||||
+1
-1
@@ -181,7 +181,7 @@ function Location(props: LocationProps) {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="text-ui-fg-interactive rounded-none pl-5 hover:bg-transparent"
|
className="text-ui-fg-interactive rounded-none pl-5 hover:bg-transparent active:bg-transparent"
|
||||||
onClick={() => navigate(`/settings/shipping/${location.id}`)}
|
onClick={() => navigate(`/settings/shipping/${location.id}`)}
|
||||||
variant="transparent"
|
variant="transparent"
|
||||||
>
|
>
|
||||||
|
|||||||
+344
@@ -0,0 +1,344 @@
|
|||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
createColumnHelper,
|
||||||
|
RowSelectionState,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import * as zod from "zod"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Heading,
|
||||||
|
IconButton,
|
||||||
|
Text,
|
||||||
|
toast,
|
||||||
|
} from "@medusajs/ui"
|
||||||
|
import { RegionCountryDTO, RegionDTO, ServiceZoneDTO } from "@medusajs/types"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { XMarkMini } from "@medusajs/icons"
|
||||||
|
|
||||||
|
import {
|
||||||
|
RouteFocusModal,
|
||||||
|
useRouteModal,
|
||||||
|
} from "../../../../../components/route-modal"
|
||||||
|
import { SplitView } from "../../../../../components/layout/split-view"
|
||||||
|
import {
|
||||||
|
useCreateServiceZone,
|
||||||
|
useUpdateServiceZone,
|
||||||
|
} from "../../../../../hooks/api/stock-locations"
|
||||||
|
import { useEffect, useMemo, useState } from "react"
|
||||||
|
import { useCountryTableQuery } from "../../../../regions/common/hooks/use-country-table-query"
|
||||||
|
import { useCountries } from "../../../../regions/common/hooks/use-countries"
|
||||||
|
import { countries as staticCountries } from "../../../../../lib/countries"
|
||||||
|
import { useDataTable } from "../../../../../hooks/use-data-table"
|
||||||
|
import { useCountryTableColumns } from "../../../../regions/common/hooks/use-country-table-columns"
|
||||||
|
import { DataTable } from "../../../../../components/table/data-table"
|
||||||
|
|
||||||
|
const PREFIX = "ac"
|
||||||
|
const PAGE_SIZE = 50
|
||||||
|
|
||||||
|
const ConditionsFooter = ({ onSave }: { onSave: () => void }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-end gap-x-2 border-t p-4">
|
||||||
|
<SplitView.Close type="button" asChild>
|
||||||
|
<Button variant="secondary" size="small">
|
||||||
|
{t("actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</SplitView.Close>
|
||||||
|
<Button size="small" type="button" onClick={onSave}>
|
||||||
|
{t("actions.select")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditeServiceZoneSchema = zod.object({
|
||||||
|
countries: zod.array(zod.string().length(2)).min(1),
|
||||||
|
})
|
||||||
|
|
||||||
|
type EditServiceZoneAreasFormProps = {
|
||||||
|
fulfillmentSetId: string
|
||||||
|
locationId: string
|
||||||
|
zone: ServiceZoneDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditServiceZoneAreasForm({
|
||||||
|
fulfillmentSetId,
|
||||||
|
locationId,
|
||||||
|
zone,
|
||||||
|
}: EditServiceZoneAreasFormProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>(
|
||||||
|
zone.geo_zones
|
||||||
|
.map((z) => z.country_code)
|
||||||
|
.reduce((acc, v) => {
|
||||||
|
acc[v] = true
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
)
|
||||||
|
|
||||||
|
const form = useForm<zod.infer<typeof EditeServiceZoneSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
countries: zone.geo_zones.map((z) => z.country_code),
|
||||||
|
},
|
||||||
|
resolver: zodResolver(EditeServiceZoneSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync: editServiceZone, isPending: isLoading } =
|
||||||
|
useUpdateServiceZone(fulfillmentSetId, zone.id, locationId)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
try {
|
||||||
|
await editServiceZone({
|
||||||
|
geo_zones: data.countries.map((iso2) => ({
|
||||||
|
country_code: iso2,
|
||||||
|
type: "country",
|
||||||
|
})),
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(t("general.error"), {
|
||||||
|
description: e.message,
|
||||||
|
dismissLabel: t("general.close"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSuccess()
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleOpenChange = (open: boolean) => {
|
||||||
|
setOpen(open)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { searchParams, raw } = useCountryTableQuery({
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
prefix: PREFIX,
|
||||||
|
})
|
||||||
|
const { countries, count } = useCountries({
|
||||||
|
countries: staticCountries.map((c, i) => ({
|
||||||
|
display_name: c.display_name,
|
||||||
|
name: c.name,
|
||||||
|
id: i as any,
|
||||||
|
iso_2: c.iso_2,
|
||||||
|
iso_3: c.iso_3,
|
||||||
|
num_code: c.num_code,
|
||||||
|
region_id: null,
|
||||||
|
region: {} as RegionDTO,
|
||||||
|
})),
|
||||||
|
...searchParams,
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = useColumns()
|
||||||
|
|
||||||
|
const { table } = useDataTable({
|
||||||
|
data: countries || [],
|
||||||
|
columns,
|
||||||
|
count,
|
||||||
|
enablePagination: true,
|
||||||
|
enableRowSelection: true,
|
||||||
|
getRowId: (row) => row.iso_2,
|
||||||
|
pageSize: PAGE_SIZE,
|
||||||
|
rowSelection: {
|
||||||
|
state: rowSelection,
|
||||||
|
updater: setRowSelection,
|
||||||
|
},
|
||||||
|
prefix: PREFIX,
|
||||||
|
})
|
||||||
|
|
||||||
|
const countriesWatch = form.watch("countries")
|
||||||
|
|
||||||
|
const onCountriesSave = () => {
|
||||||
|
form.setValue("countries", Object.keys(rowSelection))
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeCountry = (iso2: string) => {
|
||||||
|
const state = { ...rowSelection }
|
||||||
|
delete state[iso2]
|
||||||
|
setRowSelection(state)
|
||||||
|
|
||||||
|
form.setValue(
|
||||||
|
"countries",
|
||||||
|
countriesWatch.filter((c) => c !== iso2)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAll = () => {
|
||||||
|
setRowSelection({})
|
||||||
|
form.setValue("countries", [])
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedCountries = useMemo(() => {
|
||||||
|
return staticCountries.filter((c) => c.iso_2 in rowSelection)
|
||||||
|
}, [countriesWatch])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// set selected rows from form state on open
|
||||||
|
if (open) {
|
||||||
|
setRowSelection(
|
||||||
|
countriesWatch.reduce((acc, c) => {
|
||||||
|
acc[c] = true
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [open])
|
||||||
|
|
||||||
|
const showAreasError =
|
||||||
|
form.formState.errors["countries"]?.type === "too_small"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal.Form form={form}>
|
||||||
|
<form
|
||||||
|
className="flex h-full flex-col overflow-hidden"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<RouteFocusModal.Header>
|
||||||
|
<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" size="small" isLoading={isLoading}>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Header>
|
||||||
|
|
||||||
|
<RouteFocusModal.Body className="m-auto flex h-full w-full flex-col items-center divide-y overflow-hidden">
|
||||||
|
<SplitView open={open} onOpenChange={handleOpenChange}>
|
||||||
|
<SplitView.Content className="mx-auto max-w-[720px]">
|
||||||
|
<div className="container w-fit px-1 py-8">
|
||||||
|
<Heading className="mt-8 text-2xl">
|
||||||
|
{t("shipping.serviceZone.editAreasTitle", {
|
||||||
|
zone: zone.name,
|
||||||
|
})}
|
||||||
|
</Heading>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container flex items-center justify-between py-8 pr-1">
|
||||||
|
<div>
|
||||||
|
<Text weight="plus">
|
||||||
|
{t("shipping.serviceZone.areas.title")}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-ui-fg-subtle mt-2">
|
||||||
|
{t("shipping.serviceZone.areas.description")}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={() => setOpen(true)}
|
||||||
|
variant="secondary"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{t("shipping.serviceZone.areas.manage")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{!!selectedCountries.length && (
|
||||||
|
<div className="flex flex-wrap items-center gap-4">
|
||||||
|
{selectedCountries.map((c) => (
|
||||||
|
<Badge
|
||||||
|
key={c.iso_2}
|
||||||
|
className="text-ui-fg-subtle txt-small flex items-center gap-1 divide-x pr-0"
|
||||||
|
>
|
||||||
|
{c.display_name}
|
||||||
|
<IconButton
|
||||||
|
type="button"
|
||||||
|
onClick={() => removeCountry(c.iso_2)}
|
||||||
|
className="text-ui-fg-subtle p-0 px-1 pt-[1px]"
|
||||||
|
variant="transparent"
|
||||||
|
>
|
||||||
|
<XMarkMini />
|
||||||
|
</IconButton>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={clearAll}
|
||||||
|
variant="transparent"
|
||||||
|
className="txt-small text-ui-fg-muted font-medium"
|
||||||
|
>
|
||||||
|
{t("actions.clearAll")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{showAreasError && (
|
||||||
|
<Alert dismissible variant="error">
|
||||||
|
{t("shipping.serviceZone.areas.error")}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</SplitView.Content>
|
||||||
|
<SplitView.Drawer>
|
||||||
|
<div className="flex size-full flex-col overflow-hidden">
|
||||||
|
<DataTable
|
||||||
|
table={table}
|
||||||
|
columns={columns}
|
||||||
|
pageSize={PAGE_SIZE}
|
||||||
|
count={count}
|
||||||
|
search
|
||||||
|
pagination
|
||||||
|
layout="fill"
|
||||||
|
orderBy={["name", "code"]}
|
||||||
|
queryObject={raw}
|
||||||
|
prefix={PREFIX}
|
||||||
|
/>
|
||||||
|
<ConditionsFooter onSave={onCountriesSave} />
|
||||||
|
</div>
|
||||||
|
</SplitView.Drawer>
|
||||||
|
</SplitView>
|
||||||
|
</RouteFocusModal.Body>
|
||||||
|
</form>
|
||||||
|
</RouteFocusModal.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<RegionCountryDTO>()
|
||||||
|
|
||||||
|
const useColumns = () => {
|
||||||
|
const base = useCountryTableColumns()
|
||||||
|
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
columnHelper.display({
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) => {
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsSomePageRowsSelected()
|
||||||
|
? "indeterminate"
|
||||||
|
: table.getIsAllPageRowsSelected()
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) =>
|
||||||
|
table.toggleAllPageRowsSelected(!!value)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isPreselected = !row.getCanSelect()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected() || isPreselected}
|
||||||
|
disabled={isPreselected}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
...base,
|
||||||
|
],
|
||||||
|
[base]
|
||||||
|
) as ColumnDef<RegionCountryDTO>[]
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-service-zone-areas-form"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { ServiceZoneAreasEdit as Component } from "./service-zone-areas-edit"
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
import { json, useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { RouteFocusModal } from "../../../components/route-modal"
|
||||||
|
import { EditServiceZoneAreasForm } from "./components/edit-region-areas-form"
|
||||||
|
import { useStockLocation } from "../../../hooks/api/stock-locations"
|
||||||
|
|
||||||
|
export const ServiceZoneAreasEdit = () => {
|
||||||
|
const { location_id, fset_id, zone_id } = useParams()
|
||||||
|
|
||||||
|
const { stock_location, isPending, isError, error } = useStockLocation(
|
||||||
|
location_id!,
|
||||||
|
{
|
||||||
|
// NOTE: use same query for all details page subroutes & fetches
|
||||||
|
fields:
|
||||||
|
"name,*sales_channels,address.city,address.country_code,fulfillment_sets.type,fulfillment_sets.name,*fulfillment_sets.service_zones.geo_zones,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options,*fulfillment_sets.service_zones.shipping_options.rules,*fulfillment_sets.service_zones.shipping_options.shipping_profile",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const zone = stock_location?.fulfillment_sets
|
||||||
|
.find((f) => f.id === fset_id)
|
||||||
|
?.service_zones.find((z) => z.id === zone_id)
|
||||||
|
|
||||||
|
if (isError) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPending && !zone) {
|
||||||
|
throw json(
|
||||||
|
{ message: `Service zone with ID ${zone_id} was not found` },
|
||||||
|
404
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal>
|
||||||
|
{!isPending && zone && (
|
||||||
|
<EditServiceZoneAreasForm
|
||||||
|
zone={zone}
|
||||||
|
fulfillmentSetId={fset_id}
|
||||||
|
locationId={location_id}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</RouteFocusModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
+9
-1
@@ -16,10 +16,11 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
Input,
|
Input,
|
||||||
Text,
|
Text,
|
||||||
|
toast,
|
||||||
} from "@medusajs/ui"
|
} from "@medusajs/ui"
|
||||||
import { FulfillmentSetDTO, RegionCountryDTO, RegionDTO } from "@medusajs/types"
|
import { FulfillmentSetDTO, RegionCountryDTO, RegionDTO } from "@medusajs/types"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Map, XMark, XMarkMini } from "@medusajs/icons"
|
import { XMarkMini } from "@medusajs/icons"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
@@ -87,6 +88,7 @@ export function CreateServiceZoneForm({
|
|||||||
useCreateServiceZone(locationId, fulfillmentSet.id)
|
useCreateServiceZone(locationId, fulfillmentSet.id)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
try {
|
||||||
await createServiceZone({
|
await createServiceZone({
|
||||||
name: data.name,
|
name: data.name,
|
||||||
geo_zones: data.countries.map((iso2) => ({
|
geo_zones: data.countries.map((iso2) => ({
|
||||||
@@ -94,6 +96,12 @@ export function CreateServiceZoneForm({
|
|||||||
type: "country",
|
type: "country",
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(t("general.error"), {
|
||||||
|
description: e.message,
|
||||||
|
dismissLabel: t("general.close"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
handleSuccess()
|
handleSuccess()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import { StockLocationRes } from "../../../types/api-responses"
|
|||||||
import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations"
|
import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations"
|
||||||
|
|
||||||
const fulfillmentSetCreateQuery = (id: string) => ({
|
const fulfillmentSetCreateQuery = (id: string) => ({
|
||||||
queryKey: stockLocationsQueryKeys.detail(id),
|
queryKey: stockLocationsQueryKeys.detail(id, {
|
||||||
|
fields: "*fulfillment_sets",
|
||||||
|
}),
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
client.stockLocations.retrieve(id, {
|
client.stockLocations.retrieve(id, {
|
||||||
fields: "*fulfillment_sets",
|
fields: "*fulfillment_sets",
|
||||||
|
|||||||
+5
-5
@@ -45,7 +45,7 @@ type StepStatus = {
|
|||||||
[key in Tab]: ProgressStatus
|
[key in Tab]: ProgressStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
const CreateServiceZoneSchema = zod.object({
|
const CreateShippingOptionSchema = zod.object({
|
||||||
name: zod.string().min(1),
|
name: zod.string().min(1),
|
||||||
price_type: zod.nativeEnum(ShippingAllocation),
|
price_type: zod.nativeEnum(ShippingAllocation),
|
||||||
enabled_in_store: zod.boolean().optional(),
|
enabled_in_store: zod.boolean().optional(),
|
||||||
@@ -55,7 +55,7 @@ const CreateServiceZoneSchema = zod.object({
|
|||||||
currency_prices: zod.record(zod.string(), zod.string().optional()),
|
currency_prices: zod.record(zod.string(), zod.string().optional()),
|
||||||
})
|
})
|
||||||
|
|
||||||
type CreateServiceZoneFormProps = {
|
type CreateShippingOptionFormProps = {
|
||||||
zone: ServiceZoneDTO
|
zone: ServiceZoneDTO
|
||||||
isReturn?: boolean
|
isReturn?: boolean
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ type CreateServiceZoneFormProps = {
|
|||||||
export function CreateShippingOptionsForm({
|
export function CreateShippingOptionsForm({
|
||||||
zone,
|
zone,
|
||||||
isReturn,
|
isReturn,
|
||||||
}: CreateServiceZoneFormProps) {
|
}: CreateShippingOptionFormProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { handleSuccess } = useRouteModal()
|
const { handleSuccess } = useRouteModal()
|
||||||
const [tab, setTab] = React.useState<Tab>(Tab.DETAILS)
|
const [tab, setTab] = React.useState<Tab>(Tab.DETAILS)
|
||||||
@@ -77,7 +77,7 @@ export function CreateShippingOptionsForm({
|
|||||||
fields: "id,currency_code",
|
fields: "id,currency_code",
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = useForm<zod.infer<typeof CreateServiceZoneSchema>>({
|
const form = useForm<zod.infer<typeof CreateShippingOptionSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
price_type: ShippingAllocation.FlatRate,
|
price_type: ShippingAllocation.FlatRate,
|
||||||
@@ -87,7 +87,7 @@ export function CreateShippingOptionsForm({
|
|||||||
region_prices: {},
|
region_prices: {},
|
||||||
currency_prices: {},
|
currency_prices: {},
|
||||||
},
|
},
|
||||||
resolver: zodResolver(CreateServiceZoneSchema),
|
resolver: zodResolver(CreateShippingOptionSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const isCalculatedPriceType =
|
const isCalculatedPriceType =
|
||||||
|
|||||||
+4
-1
@@ -6,7 +6,10 @@ import { StockLocationRes } from "../../../types/api-responses"
|
|||||||
import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations"
|
import { stockLocationsQueryKeys } from "../../../hooks/api/stock-locations"
|
||||||
|
|
||||||
const fulfillmentSetCreateQuery = (id: string) => ({
|
const fulfillmentSetCreateQuery = (id: string) => ({
|
||||||
queryKey: stockLocationsQueryKeys.list(), // Use the list cache key for now
|
queryKey: stockLocationsQueryKeys.detail(id, {
|
||||||
|
fields:
|
||||||
|
"*fulfillment_sets,*fulfillment_sets.service_zones,*fulfillment_sets.service_zones.shipping_options",
|
||||||
|
}),
|
||||||
queryFn: async () =>
|
queryFn: async () =>
|
||||||
client.stockLocations.retrieve(id, {
|
client.stockLocations.retrieve(id, {
|
||||||
fields:
|
fields:
|
||||||
|
|||||||
+319
@@ -0,0 +1,319 @@
|
|||||||
|
import { useForm } from "react-hook-form"
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import React, { useEffect, useMemo, useState } from "react"
|
||||||
|
import * as zod from "zod"
|
||||||
|
|
||||||
|
import { Button, toast } from "@medusajs/ui"
|
||||||
|
import {
|
||||||
|
CurrencyDTO,
|
||||||
|
PriceDTO,
|
||||||
|
ProductVariantDTO,
|
||||||
|
RegionDTO,
|
||||||
|
ShippingOptionDTO,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
|
import {
|
||||||
|
RouteFocusModal,
|
||||||
|
useRouteModal,
|
||||||
|
} from "../../../../../components/route-modal"
|
||||||
|
import {
|
||||||
|
getDbAmount,
|
||||||
|
getPresentationalAmount,
|
||||||
|
} from "../../../../../lib/money-amount-helpers"
|
||||||
|
import { useRegions } from "../../../../../hooks/api/regions"
|
||||||
|
import { useStore } from "../../../../../hooks/api/store.tsx"
|
||||||
|
import { useCurrencies } from "../../../../../hooks/api/currencies"
|
||||||
|
import { ColumnDef, createColumnHelper } from "@tanstack/react-table"
|
||||||
|
import { ExtendedProductDTO } from "../../../../../types/api-responses"
|
||||||
|
import { CurrencyCell } from "../../../../../components/grid/grid-cells/common/currency-cell"
|
||||||
|
import { DataGridMeta } from "../../../../../components/grid/types"
|
||||||
|
import { DataGrid } from "../../../../../components/grid/data-grid"
|
||||||
|
import { useUpdateShippingOptions } from "../../../../../hooks/api/shipping-options.ts"
|
||||||
|
|
||||||
|
const getInitialCurrencyPrices = (prices: PriceDTO[]) => {
|
||||||
|
const ret: Record<string, number> = {}
|
||||||
|
prices.forEach((p) => {
|
||||||
|
if (p.price_rules!.length) {
|
||||||
|
// this is a region price
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ret[p.currency_code!] = getPresentationalAmount(
|
||||||
|
p.amount as number,
|
||||||
|
p.currency_code!
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const getInitialRegionPrices = (prices: PriceDTO[]) => {
|
||||||
|
const ret: Record<string, number> = {}
|
||||||
|
prices.forEach((p) => {
|
||||||
|
if (p.price_rules!.length) {
|
||||||
|
const regionId = p.price_rules![0].value
|
||||||
|
ret[regionId] = getPresentationalAmount(
|
||||||
|
p.amount as number,
|
||||||
|
p.currency_code!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const EditShippingOptionPricingSchema = zod.object({
|
||||||
|
region_prices: zod.record(
|
||||||
|
zod.string(),
|
||||||
|
zod.string().or(zod.number()).optional()
|
||||||
|
),
|
||||||
|
currency_prices: zod.record(
|
||||||
|
zod.string(),
|
||||||
|
zod.string().or(zod.number()).optional()
|
||||||
|
),
|
||||||
|
})
|
||||||
|
|
||||||
|
enum ColumnType {
|
||||||
|
REGION = "region",
|
||||||
|
CURRENCY = "currency",
|
||||||
|
}
|
||||||
|
|
||||||
|
type EnabledColumnRecord = Record<string, ColumnType>
|
||||||
|
|
||||||
|
type EditShippingOptionPricingFormProps = {
|
||||||
|
shippingOption: ShippingOptionDTO & { prices: PriceDTO[] }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditShippingOptionsPricingForm({
|
||||||
|
shippingOption,
|
||||||
|
}: EditShippingOptionPricingFormProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { handleSuccess } = useRouteModal()
|
||||||
|
|
||||||
|
const form = useForm<zod.infer<typeof EditShippingOptionPricingSchema>>({
|
||||||
|
defaultValues: {
|
||||||
|
region_prices: getInitialRegionPrices(shippingOption.prices),
|
||||||
|
currency_prices: getInitialCurrencyPrices(shippingOption.prices),
|
||||||
|
},
|
||||||
|
resolver: zodResolver(EditShippingOptionPricingSchema),
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mutateAsync, isPending: isLoading } = useUpdateShippingOptions(
|
||||||
|
shippingOption.id
|
||||||
|
)
|
||||||
|
|
||||||
|
const { regions } = useRegions()
|
||||||
|
|
||||||
|
const { store, isLoading: isStoreLoading } = useStore()
|
||||||
|
|
||||||
|
const { currencies, isLoading: isCurrenciesLoading } = useCurrencies(
|
||||||
|
{
|
||||||
|
code: store?.supported_currency_codes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
enabled: !!store,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const [enabledColumns, setEnabledColumns] = useState<EnabledColumnRecord>({})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
store?.default_currency_code &&
|
||||||
|
Object.keys(enabledColumns).length === 0
|
||||||
|
) {
|
||||||
|
setEnabledColumns({
|
||||||
|
...enabledColumns,
|
||||||
|
[store.default_currency_code]: ColumnType.CURRENCY,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [store, enabledColumns])
|
||||||
|
|
||||||
|
const columns = useColumns({
|
||||||
|
currencies,
|
||||||
|
regions,
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = useMemo(
|
||||||
|
() => [[...(currencies || []), ...(regions || [])]],
|
||||||
|
[currencies, regions]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
const currencyPrices = Object.entries(data.currency_prices)
|
||||||
|
.map(([code, value]) => {
|
||||||
|
if (value === "") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = getDbAmount(Number(value), code)
|
||||||
|
|
||||||
|
const priceRecord = {
|
||||||
|
currency_code: code,
|
||||||
|
amount: amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
const price = shippingOption.prices.find(
|
||||||
|
(p) => p.currency_code === code && !p.price_rules!.length
|
||||||
|
)
|
||||||
|
|
||||||
|
// if that currency price is already defined for the SO, we will do an update
|
||||||
|
if (price) {
|
||||||
|
priceRecord["id"] = price.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return priceRecord
|
||||||
|
})
|
||||||
|
.filter((p) => !!p)
|
||||||
|
|
||||||
|
const regionsMap = new Map(regions.map((r) => [r.id, r.currency_code]))
|
||||||
|
|
||||||
|
const regionPrices = Object.entries(data.region_prices)
|
||||||
|
.map(([region_id, value]) => {
|
||||||
|
if (value === "") {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = regionsMap.get(region_id)!
|
||||||
|
|
||||||
|
const amount = getDbAmount(Number(value), code)
|
||||||
|
|
||||||
|
const priceRecord = {
|
||||||
|
region_id,
|
||||||
|
amount: amount,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HACK - when trying to update prices which already have a region price
|
||||||
|
* we get error: `Price rule with price_id: , rule_type_id: already exist`,
|
||||||
|
* so for now, we recreate region prices.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// const price = shippingOption.prices.find(
|
||||||
|
// (p) => p.price_rules?.[0]?.value === region_id
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// if (price) {
|
||||||
|
// priceRecord["id"] = price.id
|
||||||
|
// }
|
||||||
|
|
||||||
|
return priceRecord
|
||||||
|
})
|
||||||
|
.filter((p) => !!p)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
prices: [...currencyPrices, ...regionPrices],
|
||||||
|
})
|
||||||
|
toast.error(t("general.success"), {
|
||||||
|
dismissLabel: t("general.close"),
|
||||||
|
})
|
||||||
|
handleSuccess()
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(t("general.error"), {
|
||||||
|
description: e.message,
|
||||||
|
dismissLabel: t("general.close"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const initializing =
|
||||||
|
isStoreLoading || isCurrenciesLoading || !store || !currencies
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal.Form form={form}>
|
||||||
|
<form
|
||||||
|
className="flex h-full flex-col overflow-hidden"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<RouteFocusModal.Header>
|
||||||
|
<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={handleSubmit}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{t("actions.save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Header>
|
||||||
|
|
||||||
|
<RouteFocusModal.Body className="flex h-full w-fit overflow-auto">
|
||||||
|
<div
|
||||||
|
style={{ width: "100vw" }}
|
||||||
|
className="flex size-full flex-col divide-y"
|
||||||
|
>
|
||||||
|
<DataGrid
|
||||||
|
columns={columns}
|
||||||
|
data={data}
|
||||||
|
isLoading={initializing}
|
||||||
|
state={form}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</RouteFocusModal.Body>
|
||||||
|
</form>
|
||||||
|
</RouteFocusModal.Form>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columnHelper = createColumnHelper<
|
||||||
|
ExtendedProductDTO | ProductVariantDTO
|
||||||
|
>()
|
||||||
|
|
||||||
|
const useColumns = ({
|
||||||
|
currencies = [],
|
||||||
|
regions = [],
|
||||||
|
}: {
|
||||||
|
currencies?: CurrencyDTO[]
|
||||||
|
regions?: RegionDTO[]
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const colDefs: ColumnDef<ExtendedProductDTO | ProductVariantDTO>[] =
|
||||||
|
useMemo(() => {
|
||||||
|
return [
|
||||||
|
...currencies.map((currency) => {
|
||||||
|
return columnHelper.display({
|
||||||
|
header: t("fields.priceTemplate", {
|
||||||
|
regionOrCountry: currency.code.toUpperCase(),
|
||||||
|
}),
|
||||||
|
cell: ({ row, table }) => {
|
||||||
|
return (
|
||||||
|
<CurrencyCell
|
||||||
|
currency={currency}
|
||||||
|
meta={table.options.meta as DataGridMeta<any>}
|
||||||
|
field={`currency_prices.${currency.code}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
...regions.map((region) => {
|
||||||
|
return columnHelper.display({
|
||||||
|
header: t("fields.priceTemplate", {
|
||||||
|
regionOrCountry: region.name,
|
||||||
|
}),
|
||||||
|
cell: ({ row, table }) => {
|
||||||
|
return (
|
||||||
|
<CurrencyCell
|
||||||
|
currency={currencies.find(
|
||||||
|
(c) => c.code === region.currency_code
|
||||||
|
)}
|
||||||
|
meta={table.options.meta as DataGridMeta<any>}
|
||||||
|
field={`region_prices.${region.id}`}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
}, [t, currencies, regions])
|
||||||
|
|
||||||
|
return colDefs
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export * from "./edit-shipping-options-pricing-form.tsx"
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
export { ShippingOptionsEditPricing as Component } from "./shipping-options-edit-pricing"
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
import { useParams } from "react-router-dom"
|
||||||
|
|
||||||
|
import { RouteFocusModal } from "../../../components/route-modal"
|
||||||
|
import { useShippingOptions } from "../../../hooks/api/shipping-options"
|
||||||
|
import { EditShippingOptionsPricingForm } from "./components/create-shipping-options-form"
|
||||||
|
|
||||||
|
export function ShippingOptionsEditPricing() {
|
||||||
|
const { so_id } = useParams()
|
||||||
|
|
||||||
|
const { shipping_options, isPending } = useShippingOptions({
|
||||||
|
// TODO: change this when GET option by id endpoint is implemented
|
||||||
|
id: [so_id],
|
||||||
|
fields: "*prices,*prices.price_rules",
|
||||||
|
limit: 999,
|
||||||
|
})
|
||||||
|
|
||||||
|
const shippingOption = shipping_options?.find((so) => so.id === so_id)
|
||||||
|
|
||||||
|
if (!isPending && !shippingOption) {
|
||||||
|
throw new Error(`Shipping option with id: ${so_id} not found`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RouteFocusModal>
|
||||||
|
{shippingOption && (
|
||||||
|
<EditShippingOptionsPricingForm shippingOption={shippingOption} />
|
||||||
|
)}
|
||||||
|
</RouteFocusModal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -120,6 +120,14 @@ export interface UpdateShippingOptionDTO {
|
|||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
)[]
|
)[]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The shipping option pricing
|
||||||
|
*/
|
||||||
|
prices: (
|
||||||
|
| { currency_code: string; amount: number; id?: string }
|
||||||
|
| { region_id: string; amount: number; id?: string }
|
||||||
|
)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in new issue
Block a user