225 lines
6.8 KiB
TypeScript
225 lines
6.8 KiB
TypeScript
"use client"
|
|
import { zodResolver } from "@hookform/resolvers/zod"
|
|
import { format } from "date-fns"
|
|
import { useRouter, useSearchParams } from "next/navigation"
|
|
import { Suspense, useState } from "react"
|
|
import { Controller, useForm } from "react-hook-form"
|
|
import z from "zod"
|
|
import { getBalance } from "@/actions/balance"
|
|
import { DataTable, useDataTable } from "@/components/data-table"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
Field,
|
|
FieldError,
|
|
FieldGroup,
|
|
FieldLabel,
|
|
} from "@/components/ui/field"
|
|
import { Input } from "@/components/ui/input"
|
|
import type { Balance } from "@/models/balance"
|
|
|
|
type FilterValues = {
|
|
user_phone?: string
|
|
bill_id?: string
|
|
created_at_start?: Date
|
|
created_at_end?: Date
|
|
}
|
|
|
|
const filterSchema = z
|
|
.object({
|
|
phone: z.string().optional(),
|
|
bill_id: z.string().optional(),
|
|
admin_id: z.string().optional(),
|
|
created_at_start: z.string().optional(),
|
|
created_at_end: z.string().optional(),
|
|
})
|
|
.superRefine((data, ctx) => {
|
|
if (data.created_at_start && data.created_at_end) {
|
|
const start = new Date(data.created_at_start)
|
|
const end = new Date(data.created_at_end)
|
|
|
|
if (end < start) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: "结束时间不能早于开始时间",
|
|
path: ["created_at_end"],
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
type FormValues = z.infer<typeof filterSchema>
|
|
|
|
export default function BalancePage() {
|
|
const searchParams = useSearchParams()
|
|
const router = useRouter()
|
|
const userId = searchParams.get("userId")
|
|
const userPhone = searchParams.get("phone")
|
|
console.log(userPhone, "userPhone")
|
|
|
|
const [filters, setFilters] = useState<FilterValues>({})
|
|
const { control, handleSubmit, reset } = useForm<FormValues>({
|
|
resolver: zodResolver(filterSchema),
|
|
defaultValues: {
|
|
phone: "",
|
|
bill_id: "",
|
|
admin_id: "",
|
|
created_at_start: "",
|
|
created_at_end: "",
|
|
},
|
|
})
|
|
|
|
const table = useDataTable<Balance>((page, size) =>
|
|
getBalance({ page, size, user_id: Number(userId), ...filters }),
|
|
)
|
|
console.log(table, "仅用户的table")
|
|
|
|
const onFilter = handleSubmit(data => {
|
|
const result: FilterValues = {}
|
|
if (data.phone) result.user_phone = data.phone
|
|
if (data.bill_id) result.bill_id = data.bill_id
|
|
if (data.created_at_start)
|
|
result.created_at_start = new Date(data.created_at_start)
|
|
if (data.created_at_end)
|
|
result.created_at_end = new Date(data.created_at_end)
|
|
setFilters(result)
|
|
table.pagination.onPageChange(1)
|
|
})
|
|
|
|
return (
|
|
<div className="space-y-3">
|
|
<Button
|
|
onClick={() => {
|
|
router.back()
|
|
}}
|
|
className="gap-2"
|
|
>
|
|
返回上一级
|
|
</Button>
|
|
<span className="ml-2 text-gray-600">用户会员号: {userPhone}</span>
|
|
<form onSubmit={onFilter} className="bg-white p-4 rounded-lg">
|
|
<div className="flex flex-wrap items-end gap-4">
|
|
<Controller
|
|
name="bill_id"
|
|
control={control}
|
|
render={({ field, fieldState }) => (
|
|
<Field
|
|
data-invalid={fieldState.invalid}
|
|
className="w-40 flex-none"
|
|
>
|
|
<FieldLabel>账单号</FieldLabel>
|
|
<Input {...field} placeholder="请输入账单号" />
|
|
<FieldError>{fieldState.error?.message}</FieldError>
|
|
</Field>
|
|
)}
|
|
/>
|
|
<Controller
|
|
name="created_at_start"
|
|
control={control}
|
|
render={({ field, fieldState }) => (
|
|
<Field
|
|
data-invalid={fieldState.invalid}
|
|
className="w-40 flex-none"
|
|
>
|
|
<FieldLabel>开始时间</FieldLabel>
|
|
<Input type="date" {...field} />
|
|
<FieldError>{fieldState.error?.message}</FieldError>
|
|
</Field>
|
|
)}
|
|
/>
|
|
<Controller
|
|
name="created_at_end"
|
|
control={control}
|
|
render={({ field, fieldState }) => (
|
|
<Field
|
|
data-invalid={fieldState.invalid}
|
|
className="w-40 flex-none"
|
|
>
|
|
<FieldLabel>结束时间</FieldLabel>
|
|
<Input type="date" {...field} />
|
|
<FieldError>{fieldState.error?.message}</FieldError>
|
|
</Field>
|
|
)}
|
|
/>
|
|
</div>
|
|
|
|
<FieldGroup className="flex-row justify-start mt-4 gap-2">
|
|
<Button type="submit">筛选</Button>
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
onClick={() => {
|
|
reset()
|
|
setFilters({})
|
|
table.pagination.onPageChange(1)
|
|
}}
|
|
>
|
|
重置
|
|
</Button>
|
|
</FieldGroup>
|
|
</form>
|
|
|
|
<Suspense>
|
|
<DataTable<Balance>
|
|
{...table}
|
|
columns={[
|
|
{
|
|
header: "账单号",
|
|
accessorFn: row => row.bill?.bill_no || "",
|
|
},
|
|
{
|
|
header: "管理员",
|
|
accessorKey: "admin_id",
|
|
accessorFn: row => row.admin?.name || "",
|
|
},
|
|
{
|
|
header: "变动金额",
|
|
accessorKey: "amount",
|
|
cell: ({ row }) => {
|
|
const amount = row.original.amount
|
|
const isPositive = Number(amount) > 0
|
|
return (
|
|
<div className="flex items-center gap-1">
|
|
<span
|
|
className={`font-semibold ${
|
|
isPositive ? "text-green-600" : "text-red-600"
|
|
}`}
|
|
>
|
|
{isPositive ? "+" : ""}
|
|
{Number(amount).toFixed(2)}
|
|
</span>
|
|
</div>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
header: "余额变化",
|
|
accessorKey: "balance_prev",
|
|
cell: ({ row }) => (
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-muted-foreground text-sm">
|
|
¥{Number(row.original.balance_prev).toFixed(2)}
|
|
</span>
|
|
<span className="text-muted-foreground">→</span>
|
|
<span className="font-medium text-foreground">
|
|
¥{Number(row.original.balance_curr).toFixed(2)}
|
|
</span>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
header: "备注",
|
|
accessorKey: "remark",
|
|
},
|
|
{
|
|
header: "创建时间",
|
|
accessorKey: "created_at",
|
|
cell: ({ row }) =>
|
|
format(new Date(row.original.created_at), "yyyy-MM-dd HH:mm"),
|
|
},
|
|
]}
|
|
/>
|
|
</Suspense>
|
|
</div>
|
|
)
|
|
}
|