145 lines
3.9 KiB
TypeScript
145 lines
3.9 KiB
TypeScript
"use client"
|
||
|
||
import { zodResolver } from "@hookform/resolvers/zod"
|
||
import { useState } from "react"
|
||
import { useForm } from "react-hook-form"
|
||
import { toast } from "sonner"
|
||
import { z } from "zod"
|
||
import { getDeduction } from "@/actions/cust"
|
||
import { Button } from "@/components/ui/button"
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogFooter,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from "@/components/ui/dialog"
|
||
import { Field, FieldError, FieldLabel } from "@/components/ui/field"
|
||
import { Input } from "@/components/ui/input"
|
||
import type { User } from "@/models/user"
|
||
|
||
const Schema = z.object({
|
||
deduction: z
|
||
.string()
|
||
.min(1, "请输入扣款金额")
|
||
.refine(val => !Number.isNaN(Number(val)), "请输入有效的数字")
|
||
.refine(val => Number(val) >= 0, "金额不能为负数"),
|
||
})
|
||
|
||
type FormValues = z.infer<typeof Schema>
|
||
|
||
interface UpdateDeductionDialogProps {
|
||
user: User
|
||
onSuccess: () => void
|
||
}
|
||
|
||
export function DeductionDialog({
|
||
user,
|
||
onSuccess,
|
||
}: UpdateDeductionDialogProps) {
|
||
const [open, setOpen] = useState(false)
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
|
||
const {
|
||
register,
|
||
handleSubmit,
|
||
reset,
|
||
setValue,
|
||
formState: { errors },
|
||
} = useForm<FormValues>({
|
||
resolver: zodResolver(Schema),
|
||
defaultValues: {
|
||
deduction: "",
|
||
},
|
||
})
|
||
|
||
const onSubmit = async (data: FormValues) => {
|
||
setIsLoading(true)
|
||
try {
|
||
const result = await getDeduction({
|
||
user_id: user.id,
|
||
amount: data.deduction,
|
||
})
|
||
|
||
if (result.success) {
|
||
toast.success("扣款成功")
|
||
setOpen(false)
|
||
reset()
|
||
onSuccess()
|
||
} else {
|
||
toast.error(result.message || "扣款失败")
|
||
}
|
||
} catch (error) {
|
||
const message = error instanceof Error ? error.message : error
|
||
toast.error(`网络错误,请稍后重试: ${message}`)
|
||
} finally {
|
||
setIsLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleOpenChange = (open: boolean) => {
|
||
if (!open) {
|
||
reset()
|
||
}
|
||
setOpen(open)
|
||
}
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||
<DialogTrigger asChild>
|
||
<Button size="sm">扣款</Button>
|
||
</DialogTrigger>
|
||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||
<DialogHeader>
|
||
<DialogTitle>扣款</DialogTitle>
|
||
<DialogDescription>
|
||
扣减用户 {user.name || user.username} 的余额
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
|
||
<form onSubmit={handleSubmit(onSubmit)}>
|
||
<div className="grid gap-4 py-4">
|
||
<Field data-invalid={!!errors.deduction}>
|
||
<FieldLabel>扣款(元)</FieldLabel>
|
||
<Input
|
||
type="text"
|
||
placeholder="请输入扣款金额"
|
||
{...register("deduction")}
|
||
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||
let value = e.target.value
|
||
value = value.replace(/[^\d.]/g, "")
|
||
const dotCount = (value.match(/\./g) || []).length
|
||
if (dotCount > 1) {
|
||
value = value.slice(0, value.lastIndexOf("."))
|
||
}
|
||
if (value.includes(".")) {
|
||
const [int, dec] = value.split(".")
|
||
value = `${int}.${dec.slice(0, 2)}`
|
||
}
|
||
setValue("deduction", value)
|
||
}}
|
||
/>
|
||
<FieldError>{errors.deduction?.message}</FieldError>
|
||
</Field>
|
||
</div>
|
||
|
||
<DialogFooter>
|
||
<Button
|
||
type="button"
|
||
variant="outline"
|
||
onClick={() => handleOpenChange(false)}
|
||
>
|
||
取消
|
||
</Button>
|
||
<Button type="submit" disabled={isLoading}>
|
||
{isLoading ? "保存中" : "保存"}
|
||
</Button>
|
||
</DialogFooter>
|
||
</form>
|
||
</DialogContent>
|
||
</Dialog>
|
||
)
|
||
}
|