feat(dashboard) admin 3.0 create discounts form (#6590)
This commit is contained in:
1 parent
2d00625729
commit
39e73b2d2f
5 files changed
+991
-245
No files matched your search
@@ -237,12 +237,15 @@
|
|||||||
"discounts": {
|
"discounts": {
|
||||||
"domain": "Discounts",
|
"domain": "Discounts",
|
||||||
"startDate": "Start date",
|
"startDate": "Start date",
|
||||||
|
"createDiscountTitle": "Create discount",
|
||||||
"validDuration": "Duration of the discount",
|
"validDuration": "Duration of the discount",
|
||||||
"redemptionsLimit": "Redemptions limit",
|
"redemptionsLimit": "Redemptions limit",
|
||||||
"endDate": "End date",
|
"endDate": "End date",
|
||||||
|
"type": "Discount type",
|
||||||
"percentageDiscount": "Percentage discount",
|
"percentageDiscount": "Percentage discount",
|
||||||
"freeShipping": "Free shipping",
|
"freeShipping": "Free shipping",
|
||||||
"fixedDiscount": "Fixed discount",
|
"fixedDiscount": "Fixed discount",
|
||||||
|
"fixedAmount": "Fixed amount",
|
||||||
"validRegions": "Valid regions",
|
"validRegions": "Valid regions",
|
||||||
"deleteWarning": "You are about to delete the discount {{code}}. This action cannot be undone.",
|
"deleteWarning": "You are about to delete the discount {{code}}. This action cannot be undone.",
|
||||||
"editDiscountDetails": "Edit discount details",
|
"editDiscountDetails": "Edit discount details",
|
||||||
@@ -260,6 +263,13 @@
|
|||||||
"chooseValidRegions": "Choose valid regions",
|
"chooseValidRegions": "Choose valid regions",
|
||||||
"noConditions": "No conditions are defined for this discount.",
|
"noConditions": "No conditions are defined for this discount.",
|
||||||
"editConditions": "Edit conditions",
|
"editConditions": "Edit conditions",
|
||||||
|
"conditionsHint": "Create conditions to apply on the discount",
|
||||||
|
"isTemplateDiscount": "Is this a template discount?",
|
||||||
|
"percentageDescription" : "Discount applied in %",
|
||||||
|
"fixedDescription" : "Amount discount",
|
||||||
|
"shippingDescription" : "Override delivery amount",
|
||||||
|
"selectRegionFirst": "Select region first",
|
||||||
|
"templateHint": "Template discounts allow you to define a set of rules that can be used across a group of discounts. This is useful in campaigns that should generate unique codes for each user, but where the rules for all unique codes should be the same.",
|
||||||
"conditions": {
|
"conditions": {
|
||||||
"including": {
|
"including": {
|
||||||
"products_one": "Discount applies to <0/> product.",
|
"products_one": "Discount applies to <0/> product.",
|
||||||
|
|||||||
+626
-28
@@ -1,8 +1,24 @@
|
|||||||
import { Heading, Input, Text } from "@medusajs/ui"
|
import { useEffect, useMemo } from "react"
|
||||||
|
import {
|
||||||
|
Checkbox,
|
||||||
|
DatePicker,
|
||||||
|
Heading,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
Textarea,
|
||||||
|
RadioGroup,
|
||||||
|
CurrencyInput,
|
||||||
|
} from "@medusajs/ui"
|
||||||
import { Trans, useTranslation } from "react-i18next"
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
|
import { useAdminRegions } from "medusa-react"
|
||||||
|
|
||||||
import { Form } from "../../../../../components/common/form"
|
import { Form } from "../../../../../components/common/form"
|
||||||
import { CreateDiscountFormReturn } from "./create-discount-form.tsx"
|
import { CreateDiscountFormReturn } from "./create-discount-form"
|
||||||
|
import { Combobox } from "../../../../../components/common/combobox"
|
||||||
|
import { getCurrencySymbol } from "../../../../../lib/currencies"
|
||||||
|
import { DiscountRuleType } from "./types"
|
||||||
|
|
||||||
type CreateDiscountPropsProps = {
|
type CreateDiscountPropsProps = {
|
||||||
form: CreateDiscountFormReturn
|
form: CreateDiscountFormReturn
|
||||||
@@ -11,56 +27,638 @@ type CreateDiscountPropsProps = {
|
|||||||
export const CreateDiscountDetails = ({ form }: CreateDiscountPropsProps) => {
|
export const CreateDiscountDetails = ({ form }: CreateDiscountPropsProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const { regions } = useAdminRegions()
|
||||||
|
|
||||||
|
const watchType = form.watch("type")
|
||||||
|
const watchRegion = form.watch("regions")
|
||||||
|
|
||||||
|
const isFixedDiscount = watchType === DiscountRuleType.FIXED
|
||||||
|
const isFreeShipping = watchType === DiscountRuleType.FREE_SHIPPING
|
||||||
|
|
||||||
|
const activeRegion = useMemo(() => {
|
||||||
|
if (!watchRegion || !regions?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions.find((r) => r.id === watchRegion[0])
|
||||||
|
}, [regions, watchRegion])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
form.setValue("regions", [])
|
||||||
|
form.setValue("value", undefined)
|
||||||
|
}, [watchType])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex size-full flex-col items-center overflow-auto p-16">
|
<div className="flex size-full flex-col items-center overflow-auto p-16">
|
||||||
<div className="flex w-full max-w-[736px] flex-col justify-center px-2 pb-2">
|
<div className="flex w-full max-w-[736px] flex-col justify-center px-2 pb-2">
|
||||||
<div className="flex flex-col gap-y-1">
|
<div className="flex flex-col gap-y-1">
|
||||||
<Heading>{t("discount.createDiscountTitle")}</Heading>
|
<Heading>{t("discounts.createDiscountTitle")}</Heading>
|
||||||
<Text size="small" className="text-ui-fg-subtle">
|
|
||||||
{t("discounts.createDiscountHint")}
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-y-8 divide-y [&>div]:pt-8">
|
<div className="flex flex-col gap-y-8 divide-y [&>div]:pt-8">
|
||||||
<div id="general" className="flex flex-col gap-y-8">
|
{/* DETAILS */}
|
||||||
<div className="flex flex-col gap-y-2">
|
<div className="flex flex-col gap-y-8">
|
||||||
<div className="grid grid-cols-2 gap-x-4">
|
<Form.Field
|
||||||
<Form.Field
|
control={form.control}
|
||||||
control={form.control}
|
name="type"
|
||||||
name="code"
|
render={({ field }) => {
|
||||||
render={({ field }) => {
|
return (
|
||||||
return (
|
<Form.Item>
|
||||||
<Form.Item>
|
<Form.Label>{t("discounts.type")}</Form.Label>
|
||||||
<Form.Label>{t("fields.code")}</Form.Label>
|
<Form.Control>
|
||||||
<Form.Control>
|
<RadioGroup
|
||||||
<Input {...field} />
|
className="flex justify-between gap-4"
|
||||||
</Form.Control>
|
{...field}
|
||||||
</Form.Item>
|
onValueChange={field.onChange}
|
||||||
)
|
>
|
||||||
}}
|
<RadioGroup.ChoiceBox
|
||||||
/>
|
className="flex-1"
|
||||||
|
value={DiscountRuleType.PERCENTAGE}
|
||||||
|
label={t("fields.percentage")}
|
||||||
|
description={t("discounts.percentageDescription")}
|
||||||
|
/>
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
className="flex-1"
|
||||||
|
value={DiscountRuleType.FIXED}
|
||||||
|
label={t("discounts.fixedAmount")}
|
||||||
|
description={t("discounts.fixedDescription")}
|
||||||
|
/>
|
||||||
|
<RadioGroup.ChoiceBox
|
||||||
|
className="flex-1"
|
||||||
|
value={DiscountRuleType.FREE_SHIPPING}
|
||||||
|
label={t("discounts.freeShipping")}
|
||||||
|
description={t("discounts.shippingDescription")}
|
||||||
|
/>
|
||||||
|
</RadioGroup>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="regions"
|
||||||
|
render={({ field: { onChange, value, ref, ...field } }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>
|
||||||
|
{t("discounts.chooseValidRegions")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
{isFixedDiscount ? (
|
||||||
|
<Select
|
||||||
|
value={value[0]}
|
||||||
|
onValueChange={(v) => {
|
||||||
|
if (v) {
|
||||||
|
onChange([v])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...field}
|
||||||
|
>
|
||||||
|
<Select.Trigger ref={ref}>
|
||||||
|
<Select.Value />
|
||||||
|
</Select.Trigger>
|
||||||
|
<Select.Content>
|
||||||
|
{(regions || []).map((r) => (
|
||||||
|
<Select.Item key={r.id} value={r.id}>
|
||||||
|
{r.name}
|
||||||
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Content>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Combobox
|
||||||
|
options={(regions || []).map((r) => ({
|
||||||
|
label: r.name,
|
||||||
|
value: r.id,
|
||||||
|
}))}
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="code"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label>{t("fields.code")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!isFreeShipping && (
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="value"
|
name="value"
|
||||||
render={({ field }) => {
|
render={({ field: { onChange, ...field } }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.percentage")}</Form.Label>
|
<Form.Label
|
||||||
|
tooltip={
|
||||||
|
isFixedDiscount &&
|
||||||
|
!activeRegion &&
|
||||||
|
t("discounts.selectRegionFirst")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isFixedDiscount
|
||||||
|
? t("fields.amount")
|
||||||
|
: t("fields.percentage")}
|
||||||
|
</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input {...field} />
|
{isFixedDiscount ? (
|
||||||
|
activeRegion ? (
|
||||||
|
<CurrencyInput
|
||||||
|
min={0}
|
||||||
|
onValueChange={onChange}
|
||||||
|
code={activeRegion.currency_code}
|
||||||
|
symbol={getCurrencySymbol(
|
||||||
|
activeRegion.currency_code
|
||||||
|
)}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Input key="placeholder" disabled />
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Input
|
||||||
|
key="amount"
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
{...field}
|
||||||
|
value={field.value || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
if (value === "") {
|
||||||
|
onChange(null)
|
||||||
|
} else {
|
||||||
|
onChange(parseFloat(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Text size="small" leading="compact" className="text-ui-fg-subtle">
|
||||||
|
<Trans
|
||||||
|
i18nKey="discounts.titleHint"
|
||||||
|
t={t}
|
||||||
|
components={[<br key="break" />]}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="description"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Label optional>{t("fields.description")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Textarea {...field} />
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="is_dynamic"
|
||||||
|
render={({ field: { value, onChange, ...field } }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Form.Control>
|
||||||
|
<Checkbox
|
||||||
|
checked={value}
|
||||||
|
onCheckedChange={(s) => onChange(s === true)}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.Label
|
||||||
|
className="cursor-pointer"
|
||||||
|
tooltip={t("discounts.templateHint")}
|
||||||
|
>
|
||||||
|
{t("discounts.isTemplateDiscount")}
|
||||||
|
</Form.Label>
|
||||||
|
</div>
|
||||||
|
{/*<Form.ErrorMessage />*/}
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CONFIGURATIONS */}
|
||||||
|
<div className="flex flex-col gap-y-8">
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<Text
|
||||||
|
size="large"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-base"
|
||||||
|
weight="plus"
|
||||||
|
>
|
||||||
|
{t("fields.configurations")}
|
||||||
|
</Text>
|
||||||
<Text
|
<Text
|
||||||
size="small"
|
size="small"
|
||||||
leading="compact"
|
leading="compact"
|
||||||
className="text-ui-fg-subtle"
|
className="text-ui-fg-subtle"
|
||||||
>
|
>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="discounts.titleHint"
|
|
||||||
t={t}
|
t={t}
|
||||||
|
i18nKey="discounts.codeHint"
|
||||||
|
components={[<br key="break" />]}
|
||||||
|
/>
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="start_date_enabled"
|
||||||
|
render={({ field: { value, onChange, ...field } }) => (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Form.Label tooltip="todo">
|
||||||
|
{t("discounts.hasStartDate")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Switch
|
||||||
|
{...field}
|
||||||
|
checked={!!value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
<Form.Hint className="!mt-1">
|
||||||
|
{t("discounts.startDateHint")}
|
||||||
|
</Form.Hint>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{form.watch("start_date_enabled") && (
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="start_date"
|
||||||
|
render={({
|
||||||
|
field: { value, onChange, ref: _ref, ...field },
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
<Form.Control>
|
||||||
|
<DatePicker
|
||||||
|
showTimePicker
|
||||||
|
value={value ?? undefined}
|
||||||
|
onChange={(v) => {
|
||||||
|
onChange(v ?? null)
|
||||||
|
}}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-y-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="end_date_enabled"
|
||||||
|
render={({ field: { value, onChange, ...field } }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Form.Label tooltip="todo">
|
||||||
|
{t("discounts.hasEndDate")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Switch
|
||||||
|
{...field}
|
||||||
|
checked={!!value}
|
||||||
|
onCheckedChange={onChange}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
<Form.Hint className="!mt-1">
|
||||||
|
{t("discounts.endDateHint")}
|
||||||
|
</Form.Hint>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{form.watch("end_date_enabled") && (
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="end_date"
|
||||||
|
render={({
|
||||||
|
field: { value, onChange, ref: _ref, ...field },
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
<Form.Control>
|
||||||
|
<DatePicker
|
||||||
|
showTimePicker
|
||||||
|
value={value ?? undefined}
|
||||||
|
onChange={(v) => {
|
||||||
|
onChange(v ?? null)
|
||||||
|
}}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="usage_limit_enabled"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Form.Label tooltip="todo">
|
||||||
|
{t("discounts.hasUsageLimit")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Switch
|
||||||
|
checked={!!field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
<Form.Hint className="!mt-1">
|
||||||
|
{t("discounts.usageLimitHint")}
|
||||||
|
</Form.Hint>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{form.watch("usage_limit_enabled") && (
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="usage_limit"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Control>
|
||||||
|
<div className="grid grid-cols-2 gap-x-4">
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
field.onChange(null)
|
||||||
|
} else {
|
||||||
|
field.onChange(Number(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-y-4">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="valid_duration_enabled"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Form.Label tooltip="todo">
|
||||||
|
{t("discounts.hasDurationLimit")}
|
||||||
|
</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Switch
|
||||||
|
checked={!!field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
<Form.Hint className="!mt-1">
|
||||||
|
{t("discounts.durationHint")}
|
||||||
|
</Form.Hint>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{form.watch("valid_duration_enabled") && (
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="years"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item className="flex-1">
|
||||||
|
<Form.Label>{t("fields.years")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
field.onChange(null)
|
||||||
|
} else {
|
||||||
|
field.onChange(Number(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="months"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item className="flex-1">
|
||||||
|
<Form.Label>{t("fields.months")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
field.onChange(null)
|
||||||
|
} else {
|
||||||
|
field.onChange(Number(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="days"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item className="flex-1">
|
||||||
|
<Form.Label>{t("fields.days")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
field.onChange(null)
|
||||||
|
} else {
|
||||||
|
field.onChange(Number(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="hours"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item className="flex-1">
|
||||||
|
<Form.Label>{t("fields.hours")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
field.onChange(null)
|
||||||
|
} else {
|
||||||
|
field.onChange(Number(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name="minutes"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item className="flex-1">
|
||||||
|
<Form.Label>{t("fields.minutes")}</Form.Label>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="number"
|
||||||
|
min={0}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
if (value === "") {
|
||||||
|
field.onChange(null)
|
||||||
|
} else {
|
||||||
|
field.onChange(Number(value))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
<Form.ErrorMessage />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* CONDITIONS */}
|
||||||
|
<div className="flex flex-col gap-y-8">
|
||||||
|
<div className="flex flex-col gap-y-1">
|
||||||
|
<Text
|
||||||
|
size="large"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-base"
|
||||||
|
weight="plus"
|
||||||
|
>
|
||||||
|
{t("fields.conditions")}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
size="small"
|
||||||
|
leading="compact"
|
||||||
|
className="text-ui-fg-subtle"
|
||||||
|
>
|
||||||
|
<Trans
|
||||||
|
t={t}
|
||||||
|
i18nKey="discounts.conditionsHint"
|
||||||
components={[<br key="break" />]}
|
components={[<br key="break" />]}
|
||||||
/>
|
/>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
+140
-7
@@ -2,19 +2,104 @@ import * as zod from "zod"
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { UseFormReturn, useForm } from "react-hook-form"
|
import { UseFormReturn, useForm } from "react-hook-form"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { formatISODuration } from "date-fns"
|
||||||
|
|
||||||
import { Button } from "@medusajs/ui"
|
import { Button } from "@medusajs/ui"
|
||||||
import { useAdminCreateDiscount } from "medusa-react"
|
import { useAdminCreateDiscount, useAdminRegions } from "medusa-react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
RouteFocusModal,
|
RouteFocusModal,
|
||||||
useRouteModal,
|
useRouteModal,
|
||||||
} from "../../../../../components/route-modal"
|
} from "../../../../../components/route-modal"
|
||||||
import { CreateDiscountDetails } from "./create-discount-details.tsx"
|
import { CreateDiscountDetails } from "./create-discount-details"
|
||||||
|
import { DiscountRuleType } from "./types"
|
||||||
|
import { getDbAmount } from "../../../../../lib/money-amount-helpers"
|
||||||
|
|
||||||
const CreateDiscountSchema = zod.object({
|
/**
|
||||||
code: zod.string(),
|
* There is some duplication here but we cannot achieve
|
||||||
})
|
* expected behaviour from the form with only union + discriminateUnion
|
||||||
|
*/
|
||||||
|
const CreateDiscountSchema = zod.discriminatedUnion("type", [
|
||||||
|
zod.object({
|
||||||
|
code: zod.string(),
|
||||||
|
regions: zod.array(zod.string()).min(1),
|
||||||
|
is_dynamic: zod.boolean(),
|
||||||
|
start_date: zod.date().optional(),
|
||||||
|
end_date: zod.date().optional(),
|
||||||
|
usage_limit: zod.number().optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
|
||||||
|
start_date_enabled: zod.boolean().optional(),
|
||||||
|
end_date_enabled: zod.boolean().optional(),
|
||||||
|
usage_limit_enabled: zod.boolean().optional(),
|
||||||
|
valid_duration_enabled: zod.boolean().optional(),
|
||||||
|
|
||||||
|
years: zod.number().optional(),
|
||||||
|
months: zod.number().optional(),
|
||||||
|
days: zod.number().optional(),
|
||||||
|
hours: zod.number().optional(),
|
||||||
|
minutes: zod.number().optional(),
|
||||||
|
value: zod.number().min(0).max(100),
|
||||||
|
type: zod.literal(DiscountRuleType.PERCENTAGE),
|
||||||
|
}),
|
||||||
|
zod.object({
|
||||||
|
code: zod.string(),
|
||||||
|
regions: zod.array(zod.string()).min(1),
|
||||||
|
is_dynamic: zod.boolean(),
|
||||||
|
start_date: zod.date().optional(),
|
||||||
|
end_date: zod.date().optional(),
|
||||||
|
usage_limit: zod.number().optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
|
||||||
|
start_date_enabled: zod.boolean().optional(),
|
||||||
|
end_date_enabled: zod.boolean().optional(),
|
||||||
|
usage_limit_enabled: zod.boolean().optional(),
|
||||||
|
valid_duration_enabled: zod.boolean().optional(),
|
||||||
|
|
||||||
|
years: zod.number().optional(),
|
||||||
|
months: zod.number().optional(),
|
||||||
|
days: zod.number().optional(),
|
||||||
|
hours: zod.number().optional(),
|
||||||
|
minutes: zod.number().optional(),
|
||||||
|
|
||||||
|
value: zod.union([zod.string(), zod.number()]).refine((value) => {
|
||||||
|
if (value === "") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = Number(value)
|
||||||
|
|
||||||
|
if (isNaN(num)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return num >= 0
|
||||||
|
}, "Amount must be a positive number"),
|
||||||
|
type: zod.literal(DiscountRuleType.FIXED),
|
||||||
|
}),
|
||||||
|
zod.object({
|
||||||
|
code: zod.string(),
|
||||||
|
regions: zod.array(zod.string()).min(1),
|
||||||
|
is_dynamic: zod.boolean(),
|
||||||
|
start_date: zod.date().optional(),
|
||||||
|
end_date: zod.date().optional(),
|
||||||
|
usage_limit: zod.number().optional(),
|
||||||
|
description: zod.string().optional(),
|
||||||
|
|
||||||
|
start_date_enabled: zod.boolean().optional(),
|
||||||
|
end_date_enabled: zod.boolean().optional(),
|
||||||
|
usage_limit_enabled: zod.boolean().optional(),
|
||||||
|
valid_duration_enabled: zod.boolean().optional(),
|
||||||
|
|
||||||
|
years: zod.number().optional(),
|
||||||
|
months: zod.number().optional(),
|
||||||
|
days: zod.number().optional(),
|
||||||
|
hours: zod.number().optional(),
|
||||||
|
minutes: zod.number().optional(),
|
||||||
|
value: zod.undefined(),
|
||||||
|
type: zod.literal(DiscountRuleType.FREE_SHIPPING),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
type Schema = zod.infer<typeof CreateDiscountSchema>
|
type Schema = zod.infer<typeof CreateDiscountSchema>
|
||||||
export type CreateDiscountFormReturn = UseFormReturn<Schema>
|
export type CreateDiscountFormReturn = UseFormReturn<Schema>
|
||||||
@@ -25,17 +110,65 @@ export const CreateDiscountForm = () => {
|
|||||||
|
|
||||||
const form = useForm<Schema>({
|
const form = useForm<Schema>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
code: "",
|
regions: [],
|
||||||
|
is_dynamic: false,
|
||||||
|
type: DiscountRuleType.PERCENTAGE,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(CreateDiscountSchema),
|
resolver: zodResolver(CreateDiscountSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { mutateAsync, isLoading } = useAdminCreateDiscount()
|
const { mutateAsync, isLoading } = useAdminCreateDiscount()
|
||||||
|
const { regions } = useAdminRegions()
|
||||||
|
|
||||||
|
const handleSubmit = form.handleSubmit(async (values: Schema) => {
|
||||||
|
const getValue = () => {
|
||||||
|
if (values.type === DiscountRuleType.FREE_SHIPPING) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.type === DiscountRuleType.PERCENTAGE) {
|
||||||
|
return values.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount =
|
||||||
|
typeof values.value === "string" ? Number(values.value) : values.value
|
||||||
|
|
||||||
|
const region = regions!.find((r) => r.id === values.regions[0])
|
||||||
|
|
||||||
|
return getDbAmount(amount, region!.currency_code)
|
||||||
|
}
|
||||||
|
|
||||||
|
const duration = {
|
||||||
|
years: values.years,
|
||||||
|
months: values.months,
|
||||||
|
days: values.days,
|
||||||
|
hours: values.hours,
|
||||||
|
minutes: values.minutes,
|
||||||
|
}
|
||||||
|
|
||||||
|
const isDurationEmpty = Object.values(duration).every((v) => !v)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (values) => {
|
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
code: values.code,
|
code: values.code,
|
||||||
|
regions: values.regions,
|
||||||
|
is_dynamic: values.is_dynamic,
|
||||||
|
starts_at: values.start_date_enabled ? values.start_date : undefined,
|
||||||
|
ends_at: values.end_date_enabled ? values.end_date : undefined,
|
||||||
|
is_disabled: false,
|
||||||
|
usage_limit: values.usage_limit_enabled
|
||||||
|
? values.usage_limit
|
||||||
|
: undefined,
|
||||||
|
valid_duration:
|
||||||
|
values.valid_duration_enabled && !isDurationEmpty
|
||||||
|
? formatISODuration(duration)
|
||||||
|
: undefined,
|
||||||
|
rule: {
|
||||||
|
value: getValue(),
|
||||||
|
type: values.type,
|
||||||
|
description: values.description,
|
||||||
|
allocation: "total" as any,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: ({ discount }) => {
|
onSuccess: ({ discount }) => {
|
||||||
|
|||||||
+5
@@ -0,0 +1,5 @@
|
|||||||
|
export enum DiscountRuleType {
|
||||||
|
FIXED = "fixed",
|
||||||
|
PERCENTAGE = "percentage",
|
||||||
|
FREE_SHIPPING = "free_shipping",
|
||||||
|
}
|
||||||
+210
-210
@@ -79,16 +79,18 @@ export const EditDiscountConfigurationForm = ({
|
|||||||
const { mutateAsync, isLoading } = useAdminUpdateDiscount(discount.id)
|
const { mutateAsync, isLoading } = useAdminUpdateDiscount(discount.id)
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
|
const duration = pick(data, ["years", "months", "days", "hours", "minutes"])
|
||||||
|
const isDurationEmpty = Object.values(duration).every((v) => !v)
|
||||||
|
|
||||||
await mutateAsync(
|
await mutateAsync(
|
||||||
{
|
{
|
||||||
starts_at: data.start_date,
|
starts_at: data.start_date,
|
||||||
ends_at: data.end_date_enabled ? data.end_date : null,
|
ends_at: data.end_date_enabled ? data.end_date : null,
|
||||||
usage_limit: data.enable_usage_limit ? data.usage_limit : null,
|
usage_limit: data.enable_usage_limit ? data.usage_limit : null,
|
||||||
valid_duration: data.enable_duration
|
valid_duration:
|
||||||
? formatISODuration(
|
data.enable_duration && !isDurationEmpty
|
||||||
pick(data, ["years", "months", "days", "hours", "minutes"])
|
? formatISODuration(duration)
|
||||||
)
|
: null,
|
||||||
: null,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -189,40 +191,34 @@ export const EditDiscountConfigurationForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Form.Field
|
{form.watch("end_date_enabled") && (
|
||||||
control={form.control}
|
<Form.Field
|
||||||
name="end_date"
|
control={form.control}
|
||||||
render={({
|
name="end_date"
|
||||||
field: { value, onChange, ref: _ref, ...field },
|
render={({
|
||||||
}) => {
|
field: { value, onChange, ref: _ref, ...field },
|
||||||
return (
|
}) => {
|
||||||
<Form.Item>
|
return (
|
||||||
<div className="flex items-center justify-between">
|
<Form.Item>
|
||||||
<Form.Control>
|
<div className="flex items-center justify-between">
|
||||||
<DatePicker
|
<Form.Control>
|
||||||
showTimePicker
|
<DatePicker
|
||||||
value={value ?? undefined}
|
showTimePicker
|
||||||
onChange={(v) => {
|
value={value ?? undefined}
|
||||||
onChange(v ?? null)
|
onChange={(v) => {
|
||||||
}}
|
onChange(v ?? null)
|
||||||
{...field}
|
}}
|
||||||
/**
|
{...field}
|
||||||
* TODO: FIX bug in the picker when a placeholder is provided it resets selected value to undefined
|
/>
|
||||||
*/
|
</Form.Control>
|
||||||
// placeholder="DD/MM/YYYY HH:MM"
|
</div>
|
||||||
/*
|
|
||||||
* Disable input here. If set on Field it wont properly set the value.
|
|
||||||
*/
|
|
||||||
disabled={!form.watch("end_date_enabled")}
|
|
||||||
/>
|
|
||||||
</Form.Control>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-4">
|
||||||
@@ -254,34 +250,35 @@ export const EditDiscountConfigurationForm = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Form.Field
|
{form.watch("enable_usage_limit") && (
|
||||||
control={form.control}
|
<Form.Field
|
||||||
name="usage_limit"
|
control={form.control}
|
||||||
render={({ field }) => {
|
name="usage_limit"
|
||||||
return (
|
render={({ field }) => {
|
||||||
<Form.Item>
|
return (
|
||||||
<Form.Control>
|
<Form.Item>
|
||||||
<Input
|
<Form.Control>
|
||||||
{...field}
|
<Input
|
||||||
type="number"
|
{...field}
|
||||||
min={0}
|
type="number"
|
||||||
disabled={!form.watch("enable_usage_limit")}
|
min={0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
field.onChange(null)
|
field.onChange(null)
|
||||||
} else {
|
} else {
|
||||||
field.onChange(Number(value))
|
field.onChange(Number(value))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-4">
|
||||||
@@ -312,155 +309,158 @@ export const EditDiscountConfigurationForm = ({
|
|||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-between gap-3">
|
{form.watch("enable_duration") && (
|
||||||
<Form.Field
|
<div className="flex items-center justify-between gap-3">
|
||||||
control={form.control}
|
<Form.Field
|
||||||
name="years"
|
control={form.control}
|
||||||
render={({ field }) => {
|
name="years"
|
||||||
return (
|
render={({ field }) => {
|
||||||
<Form.Item className="flex-1">
|
return (
|
||||||
<Form.Label>{t("fields.years")}</Form.Label>
|
<Form.Item className="flex-1">
|
||||||
<Form.Control>
|
<Form.Label>{t("fields.years")}</Form.Label>
|
||||||
<Input
|
<Form.Control>
|
||||||
{...field}
|
<Input
|
||||||
type="number"
|
{...field}
|
||||||
min={0}
|
type="number"
|
||||||
disabled={!form.watch("enable_duration")}
|
min={0}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
field.onChange(null)
|
field.onChange(null)
|
||||||
} else {
|
} else {
|
||||||
field.onChange(Number(value))
|
field.onChange(Number(value))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="months"
|
name="months"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item className="flex-1">
|
<Form.Item className="flex-1">
|
||||||
<Form.Label>{t("fields.months")}</Form.Label>
|
<Form.Label>{t("fields.months")}</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
disabled={!form.watch("enable_duration")}
|
disabled={!form.watch("enable_duration")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
field.onChange(null)
|
field.onChange(null)
|
||||||
} else {
|
} else {
|
||||||
field.onChange(Number(value))
|
field.onChange(Number(value))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="days"
|
name="days"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item className="flex-1">
|
<Form.Item className="flex-1">
|
||||||
<Form.Label>{t("fields.days")}</Form.Label>
|
<Form.Label>{t("fields.days")}</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
disabled={!form.watch("enable_duration")}
|
disabled={!form.watch("enable_duration")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
field.onChange(null)
|
field.onChange(null)
|
||||||
} else {
|
} else {
|
||||||
field.onChange(Number(value))
|
field.onChange(Number(value))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3">
|
)}
|
||||||
<Form.Field
|
{form.watch("enable_duration") && (
|
||||||
control={form.control}
|
<div className="flex items-center gap-3">
|
||||||
name="hours"
|
<Form.Field
|
||||||
render={({ field }) => {
|
control={form.control}
|
||||||
return (
|
name="hours"
|
||||||
<Form.Item className="flex-1">
|
render={({ field }) => {
|
||||||
<Form.Label>{t("fields.hours")}</Form.Label>
|
return (
|
||||||
<Form.Control>
|
<Form.Item className="flex-1">
|
||||||
<Input
|
<Form.Label>{t("fields.hours")}</Form.Label>
|
||||||
{...field}
|
<Form.Control>
|
||||||
type="number"
|
<Input
|
||||||
min={0}
|
{...field}
|
||||||
disabled={!form.watch("enable_duration")}
|
type="number"
|
||||||
onChange={(e) => {
|
min={0}
|
||||||
const value = e.target.value
|
disabled={!form.watch("enable_duration")}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value
|
||||||
|
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
field.onChange(null)
|
field.onChange(null)
|
||||||
} else {
|
} else {
|
||||||
field.onChange(Number(value))
|
field.onChange(Number(value))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="minutes"
|
name="minutes"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item className="flex-1">
|
<Form.Item className="flex-1">
|
||||||
<Form.Label>{t("fields.minutes")}</Form.Label>
|
<Form.Label>{t("fields.minutes")}</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
min={0}
|
min={0}
|
||||||
disabled={!form.watch("enable_duration")}
|
disabled={!form.watch("enable_duration")}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value
|
const value = e.target.value
|
||||||
if (value === "") {
|
if (value === "") {
|
||||||
field.onChange(null)
|
field.onChange(null)
|
||||||
} else {
|
} else {
|
||||||
console.log(Number(value))
|
console.log(Number(value))
|
||||||
field.onChange(Number(value))
|
field.onChange(Number(value))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</RouteDrawer.Body>
|
</RouteDrawer.Body>
|
||||||
|
|||||||
Reference in new issue
Block a user