261 lines
8.4 KiB
TypeScript
261 lines
8.4 KiB
TypeScript
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { Button } from "@workspace/ui/components/button";
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
FormItem,
|
|
FormLabel,
|
|
FormMessage,
|
|
} from "@workspace/ui/components/form";
|
|
import {
|
|
Sheet,
|
|
SheetContent,
|
|
SheetFooter,
|
|
SheetHeader,
|
|
SheetTitle,
|
|
SheetTrigger,
|
|
} from "@workspace/ui/components/sheet";
|
|
import { Combobox } from "@workspace/ui/composed/combobox";
|
|
import { EnhancedInput } from "@workspace/ui/composed/enhanced-input";
|
|
import { unitConversion } from "@workspace/ui/utils/unit-conversions";
|
|
import { useState } from "react";
|
|
import { useForm } from "react-hook-form";
|
|
import { useTranslation } from "react-i18next";
|
|
import { z } from "zod";
|
|
import {
|
|
formatCurrency,
|
|
getSubscribeDiscounts,
|
|
getSubscribeUnitPrice,
|
|
} from "./utils";
|
|
|
|
const priceSchema = z.object({
|
|
promo_rule_id: z.number().min(1),
|
|
subscribe_id: z.number().min(1),
|
|
quantity: z.number().min(1),
|
|
promo_price: z.number().min(1),
|
|
});
|
|
|
|
type PriceFormValues = z.infer<typeof priceSchema>;
|
|
|
|
interface PriceFormProps {
|
|
loading?: boolean;
|
|
rules: API.PromoRule[];
|
|
subscribes: API.SubscribeItem[];
|
|
onSubmit: (data: API.CreatePromoPriceRequest) => Promise<boolean> | boolean;
|
|
}
|
|
|
|
export default function PriceForm({
|
|
loading,
|
|
rules,
|
|
subscribes,
|
|
onSubmit,
|
|
}: PriceFormProps) {
|
|
const { t } = useTranslation("promo");
|
|
const [open, setOpen] = useState(false);
|
|
const form = useForm<PriceFormValues>({
|
|
resolver: zodResolver(priceSchema),
|
|
defaultValues: {
|
|
promo_rule_id: 0,
|
|
subscribe_id: 0,
|
|
quantity: 0,
|
|
promo_price: 0,
|
|
},
|
|
});
|
|
const subscribeId = form.watch("subscribe_id");
|
|
const quantity = form.watch("quantity");
|
|
const subscribe = subscribes.find((item) => item.id === subscribeId);
|
|
const discounts = getSubscribeDiscounts(subscribe);
|
|
const unitPrice = getSubscribeUnitPrice(subscribe);
|
|
const originalPrice = unitPrice * quantity;
|
|
const hasSelectedSubscribe = subscribeId > 0;
|
|
const hasDiscounts = discounts.length > 0;
|
|
|
|
async function handleSubmit(values: PriceFormValues) {
|
|
const maxPrice = unitPrice * values.quantity;
|
|
if (maxPrice > 0 && values.promo_price >= maxPrice) {
|
|
form.setError("promo_price", {
|
|
message: t(
|
|
"form.promoPriceLessThanOriginalPrice",
|
|
"Promo price must be lower than original price"
|
|
),
|
|
});
|
|
return;
|
|
}
|
|
const success = await onSubmit({
|
|
promo_rule_id: values.promo_rule_id,
|
|
items: [
|
|
{
|
|
subscribe_id: values.subscribe_id,
|
|
quantity: values.quantity,
|
|
promo_price: values.promo_price,
|
|
},
|
|
],
|
|
});
|
|
if (success) setOpen(false);
|
|
}
|
|
|
|
return (
|
|
<Sheet onOpenChange={setOpen} open={open}>
|
|
<SheetTrigger asChild>
|
|
<Button
|
|
onClick={() => {
|
|
form.reset({
|
|
promo_rule_id: 0,
|
|
subscribe_id: 0,
|
|
quantity: 0,
|
|
promo_price: 0,
|
|
});
|
|
setOpen(true);
|
|
}}
|
|
size="sm"
|
|
>
|
|
{t("createPrice", "Create Promo Price")}
|
|
</Button>
|
|
</SheetTrigger>
|
|
<SheetContent className="w-[520px] max-w-full md:max-w-screen-md">
|
|
<SheetHeader>
|
|
<SheetTitle>{t("createPrice", "Create Promo Price")}</SheetTitle>
|
|
</SheetHeader>
|
|
<Form {...form}>
|
|
<form
|
|
className="space-y-4 px-6 pt-4"
|
|
id="promo-price-form"
|
|
onSubmit={form.handleSubmit(handleSubmit)}
|
|
>
|
|
<FormField
|
|
control={form.control}
|
|
name="promo_rule_id"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{t("rule", "Rule")}</FormLabel>
|
|
<FormControl>
|
|
<Combobox<number>
|
|
onChange={field.onChange}
|
|
options={rules.map((rule) => ({
|
|
label: rule.name,
|
|
value: rule.id,
|
|
}))}
|
|
placeholder={t("form.selectRule", "Select rule")}
|
|
value={field.value}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="subscribe_id"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{t("subscribe", "Subscribe")}</FormLabel>
|
|
<FormControl>
|
|
<Combobox<number>
|
|
onChange={(value) => {
|
|
field.onChange(value);
|
|
form.setValue("quantity", 0, {
|
|
shouldDirty: true,
|
|
shouldValidate: true,
|
|
});
|
|
form.clearErrors(["quantity", "promo_price"]);
|
|
}}
|
|
options={subscribes
|
|
.filter((item) => typeof item.id === "number")
|
|
.map((item) => ({
|
|
label: item.name || `#${item.id}`,
|
|
value: item.id as number,
|
|
}))}
|
|
placeholder={t(
|
|
"form.selectSubscribe",
|
|
"Select subscribe"
|
|
)}
|
|
value={field.value}
|
|
/>
|
|
</FormControl>
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="quantity"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{t("quantity", "Quantity")}</FormLabel>
|
|
<FormControl>
|
|
<Combobox<number>
|
|
onChange={field.onChange}
|
|
options={discounts.map((item) => ({
|
|
label: `${item.quantity} x ${t(
|
|
subscribe?.unit_time || "Month",
|
|
subscribe?.unit_time || "Month"
|
|
)}`,
|
|
value: item.quantity,
|
|
}))}
|
|
placeholder={t(
|
|
"form.selectQuantity",
|
|
"Select discount quantity"
|
|
)}
|
|
value={field.value}
|
|
/>
|
|
</FormControl>
|
|
{hasSelectedSubscribe && !hasDiscounts && (
|
|
<p className="text-destructive text-xs">
|
|
{t(
|
|
"form.noDiscounts",
|
|
"This subscribe has no discount quantities and cannot be configured."
|
|
)}
|
|
</p>
|
|
)}
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
<FormField
|
|
control={form.control}
|
|
name="promo_price"
|
|
render={({ field }) => (
|
|
<FormItem>
|
|
<FormLabel>{t("promoPrice", "Promo Price")}</FormLabel>
|
|
<FormControl>
|
|
<EnhancedInput
|
|
formatInput={(value) =>
|
|
unitConversion("centsToDollars", value)
|
|
}
|
|
formatOutput={(value) =>
|
|
unitConversion("dollarsToCents", value)
|
|
}
|
|
min={0.01}
|
|
onValueChange={(value) => field.onChange(value)}
|
|
placeholder={t("form.enterPromoPrice", "Enter price")}
|
|
type="number"
|
|
value={field.value}
|
|
/>
|
|
</FormControl>
|
|
{originalPrice > 0 && (
|
|
<p className="text-muted-foreground text-xs">
|
|
{t("originalPriceHint", "Original price")}:{" "}
|
|
{formatCurrency(originalPrice)}
|
|
</p>
|
|
)}
|
|
<FormMessage />
|
|
</FormItem>
|
|
)}
|
|
/>
|
|
</form>
|
|
</Form>
|
|
<SheetFooter>
|
|
<Button
|
|
disabled={loading || (hasSelectedSubscribe && !hasDiscounts)}
|
|
form="promo-price-form"
|
|
type="submit"
|
|
>
|
|
{t("confirm", "Confirm")}
|
|
</Button>
|
|
</SheetFooter>
|
|
</SheetContent>
|
|
</Sheet>
|
|
);
|
|
}
|