新功能(#113): 新增促销管理后台页面
PR Check / Lint, Check, Test and Build (push) Has been cancelled

This commit is contained in:
2026-05-29 00:34:37 -07:00
parent 2a3d5a3234
commit c46944aae1
17 changed files with 1733 additions and 0 deletions
@@ -0,0 +1,188 @@
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 { getSubscribeUnitPrice } from "./utils";
const priceSchema = z.object({
rule_id: z.number().min(1),
subscribe_id: 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: {
rule_id: 0,
subscribe_id: 0,
promo_price: 0,
},
});
const subscribeId = form.watch("subscribe_id");
const subscribe = subscribes.find((item) => item.id === subscribeId);
const unitPrice = getSubscribeUnitPrice(subscribe);
async function handleSubmit(values: PriceFormValues) {
if (unitPrice > 0 && values.promo_price >= unitPrice) {
form.setError("promo_price", {
message: t(
"form.promoPriceLessThanUnitPrice",
"Promo price must be lower than unit price"
),
});
return;
}
const success = await onSubmit(values);
if (success) setOpen(false);
}
return (
<Sheet onOpenChange={setOpen} open={open}>
<SheetTrigger asChild>
<Button
onClick={() => {
form.reset({ rule_id: 0, subscribe_id: 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="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={field.onChange}
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="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>
{unitPrice > 0 && (
<p className="text-muted-foreground text-xs">
{t("unitPriceHint", "Unit price")}:{" "}
{unitConversion("centsToDollars", unitPrice)}
</p>
)}
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
<SheetFooter>
<Button disabled={loading} form="promo-price-form" type="submit">
{t("confirm", "Confirm")}
</Button>
</SheetFooter>
</SheetContent>
</Sheet>
);
}