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