Files
admin/src/app/(root)/coupon/update.tsx

303 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { zodResolver } from "@hookform/resolvers/zod"
import { useState } from "react"
import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner"
import z from "zod"
import { updateCoupon } from "@/actions/coupon"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogClose,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import type { Coupon } from "@/models/coupon"
const schema = z.object({
name: z.string().min(1, "请输入优惠券名称"),
amount: z.string()
.min(1, "请输入优惠券金额")
.regex(/^\d+(\.\d+)?$/, "请输入有效的金额数字")
.refine(val => Number(val) > 0, "优惠券金额必须大于0"),
count: z.string()
.min(1, "请输入优惠券数量")
.regex(/^\d+$/, "请输入正整数")
.refine(val => Number(val) >= 1, "优惠券数量至少为1"),
min_amount: z.string()
.min(1, "请输入最低消费金额")
.regex(/^\d+(\.\d+)?$/, "请输入有效的金额数字")
.refine(val => Number(val) >= 0, "最低消费金额不能为负数"),
expire_at: z.string().optional(),
expire_type: z.string().min(1, "请选择过期类型"),
expire_in: z.string().optional(),
status:z.string().optional()
}).superRefine((data, ctx) => {
const expireType = Number(data.expire_type);
if (!data.expire_type) return;
if (expireType === 1 && !data.expire_at) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "请选择过期时间",
path: ["expire_at"]
});
}
if (expireType === 2 && !data.expire_in) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "请输入过期时长天数",
path: ["expire_in"]
});
}
})
export function UpdateCoupon(props: {
coupon: Coupon
onSuccess?: () => void
}) {
const [open, setOpen] = useState(false)
const form = useForm({
resolver: zodResolver(schema),
defaultValues: {
name: props.coupon.name,
amount: String(props.coupon.amount),
min_amount: String(props.coupon.min_amount),
expire_at: props?.coupon.expire_at
? new Date(props?.coupon.expire_at).toISOString().split("T")[0]
: "",
status: String(props.coupon.status),
count: String(props.coupon.count),
expire_in: String(props?.coupon.expire_in),
},
mode: "onChange",
})
const { control, handleSubmit, reset, watch } = form
const watchExpireType = watch("expire_type")
const onSubmit = async (data: z.infer<typeof schema>) => {
try {
const expireType = Number(data.expire_type)
const payload = {
id: props.coupon.id,
name: data.name,
amount: Number(data.amount),
min_amount: Number(data.min_amount),
count: Number(data.count),
status: Number(data.status),
expire_type: expireType,
expire_at: data.expire_at ? new Date(data.expire_at) : undefined,
expire_in: expireType === 2 ? Number(data.expire_in) : undefined,
}
const resp = await updateCoupon(payload)
if (resp.success) {
toast.success("优惠券修改成功")
props.onSuccess?.()
setOpen(false)
} else {
toast.error(resp.message)
}
} catch (error) {
const message = error instanceof Error ? error.message : error
toast.error(`接口请求错误: ${message}`)
}
}
const handleOpenChange = (value: boolean) => {
if (value) {
reset({
name: props.coupon.name,
count: String(props.coupon.count),
amount: String(props.coupon.amount ),
min_amount: String(props.coupon.min_amount),
expire_at: props.coupon.expire_at
? new Date(props.coupon.expire_at).toISOString().split("T")[0]
: "",
status: String(props.coupon.status),
expire_type: String(props.coupon.expire_type),
expire_in: String(props.coupon.expire_in),
})
}
setOpen(value)
}
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger asChild>
<Button size="sm" variant="secondary">
</Button>
</DialogTrigger>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle></DialogTitle>
</DialogHeader>
<form id="coupon-update" onSubmit={handleSubmit(onSubmit)}>
<FieldGroup>
<Controller
control={control}
name="name"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Input {...field} />
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
<Controller
control={control}
name="count"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Input {...field} />
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
<Controller
control={control}
name="amount"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Input {...field} />
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
<Controller
control={control}
name="min_amount"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Input {...field} />
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
<Controller
control={control}
name="status"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="请选择状态" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0"></SelectItem>
<SelectItem value="1"></SelectItem>
</SelectContent>
</Select>
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
<Controller
control={control}
name="expire_type"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Select value={field.value} onValueChange={field.onChange}>
<SelectTrigger>
<SelectValue placeholder="请选择过期类型" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0"></SelectItem>
<SelectItem value="1"></SelectItem>
<SelectItem value="2"></SelectItem>
</SelectContent>
</Select>
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
{watchExpireType === "1" && (
<Controller
control={control}
name="expire_at"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Input
type="date"
min={new Date().toISOString().split("T")[0]}
{...field}
/>
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
)}
{watchExpireType === "2" && (
<Controller
control={control}
name="expire_in"
render={({ field, fieldState }) => (
<div className="flex items-start gap-4">
<FieldLabel className="w-28 pt-2">:</FieldLabel>
<div className="flex-1">
<Input
type="number"
min="0"
placeholder="请输入过期天数"
{...field}
/>
<FieldError>{fieldState.error?.message}</FieldError>
</div>
</div>
)}
/>
)}
</FieldGroup>
</form>
<DialogFooter>
<DialogClose asChild>
<Button variant="ghost"></Button>
</DialogClose>
<Button type="submit" form="coupon-update">
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}