diff --git a/src/actions/coupon.ts b/src/actions/coupon.ts index 7198091..6a17a89 100644 --- a/src/actions/coupon.ts +++ b/src/actions/coupon.ts @@ -39,9 +39,20 @@ export async function deleteCoupon(id: number) { }) } -export async function issueCoupon(data: { +export async function getReleaseCoupon(data: { coupon_id: number user_id: number }) { return callByUser("/api/admin/coupon/update/assign", data) } + +export async function getUserCoupon(data: { + page: number + size: number + user_id: number +}) { + return callByUser>( + "/api/admin/coupon-user/page/of-user", + data, + ) +} diff --git a/src/app/(root)/_navigation/index.tsx b/src/app/(root)/_navigation/index.tsx index f48a74d..9045a70 100644 --- a/src/app/(root)/_navigation/index.tsx +++ b/src/app/(root)/_navigation/index.tsx @@ -24,7 +24,7 @@ import { } from "lucide-react" import Link from "next/link" import { usePathname } from "next/navigation" -import { createContext, type ReactNode, useContext, useState } from "react" +import { createContext, type ReactNode, Suspense, useContext, useState } from "react" import { twJoin } from "tailwind-merge" import { Auth } from "@/components/auth" import { Button } from "@/components/ui/button" @@ -307,7 +307,7 @@ export default function Navigation() { )} > {/*Logo 区域 */} - + {/* Navigation Menu */} diff --git a/src/app/(root)/_navigation/logo.tsx b/src/app/(root)/_navigation/logo.tsx index 352e2d9..c194b14 100644 --- a/src/app/(root)/_navigation/logo.tsx +++ b/src/app/(root)/_navigation/logo.tsx @@ -1,3 +1,4 @@ + import { ComputerIcon } from "lucide-react" import { getNodeEnv } from "@/actions/env" import { cn } from "@/lib/utils" diff --git a/src/app/(root)/client/coupon/coupon.tsx b/src/app/(root)/client/coupon/coupon.tsx new file mode 100644 index 0000000..dab463b --- /dev/null +++ b/src/app/(root)/client/coupon/coupon.tsx @@ -0,0 +1,109 @@ +"use client" +import { useState } from "react" +import { toast } from "sonner" +import { getPagCoupon } from "@/actions/coupon" +import { DataTable, useDataTable } from "@/components/data-table" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import type { Coupon } from "@/models/coupon" + +export function IssueCouponForUser(props: { + userId: number + userPhone: string + onSuccess?: () => void +}) { + const [open, setOpen] = useState(false) + + const table = useDataTable((page, size) => + getPagCoupon({ page, size }), + ) + + const handleIssue = async (coupon: Coupon) => { + // try { + // const result = await issueCouponToUser({ + // user_id: props.userId, + // coupon_id: coupon.id, + // coupon_name: coupon.name, + // }) + // if (result.success) { + // toast.success(`成功发放「${coupon.name}」给用户 ${props.userPhone}`) + // props.onSuccess?.() + // setOpen(false) + // } else { + // toast.error(result.message || "发放失败") + // } + // } catch (error) { + // const message = error instanceof Error ? error.message : "发放失败" + // toast.error(`发放优惠券失败: ${message}`) + // } + } + + return ( + { + setOpen(newOpen) + if (newOpen) { + table.refresh() + } + }} + > + + + + + + 发放优惠券给 {props.userPhone} + + + {...table} + columns={[ + { header: "优惠券名称", accessorKey: "name" }, + { header: "优惠券金额", accessorKey: "amount" }, + { header: "最低消费金额", accessorKey: "min_amount" }, + { + header: "状态", + accessorKey: "status", + cell: ({ row }) => { + const status = row.original.status + if (status === 0) { + return 禁用 + } + if (status === 1) { + return 正常 + } + return - + }, + }, + { + id: "action", + meta: { pin: "right" }, + header: "操作", + cell: ({ row }) => ( + + ), + }, + ]} + /> + + + + + + + + ) +} diff --git a/src/app/(root)/client/coupon/page.tsx b/src/app/(root)/client/coupon/page.tsx new file mode 100644 index 0000000..18ee1c8 --- /dev/null +++ b/src/app/(root)/client/coupon/page.tsx @@ -0,0 +1,275 @@ +"use client" +import { format } from "date-fns" +import { useRouter, useSearchParams } from "next/navigation" +import { Suspense, useState } from "react" +import { toast } from "sonner" +import { getUserCoupon } from "@/actions/coupon" +import { DataTable, useDataTable } from "@/components/data-table" +import { Page } from "@/components/page" +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import type { Coupon } from "@/models/coupon" + +export default function CouponPage() { + const router = useRouter() + const searchParams = useSearchParams() + const userId = searchParams.get("userId") + const userPhone = searchParams.get("phone") + + const table = useDataTable((page, size) => { + return getUserCoupon({ + user_id: Number(userId), + page, + size, + }) + }) + console.log(table, "table") + + const expireLabel = (coupon: Coupon) => { + switch (coupon.expire_type) { + case 0: + return "永久有效" + case 1: + return coupon.expire_at + ? format(new Date(coupon.expire_at), "yyyy-MM-dd") + : "" + case 2: + return coupon.expire_in ? `发放后${coupon.expire_in}天` : "" + default: + return "" + } + } + + return ( + +
+
+ + 用户手机号: {userPhone} +
+
+ + + + {...table} + columns={[ + { header: "优惠券名称", accessorKey: "name" }, + { header: "券码", accessorKey: "code" }, + { header: "金额", accessorKey: "amount" }, + { header: "最低消费", accessorKey: "min_amount" }, + { + header: "状态", + accessorKey: "coupon_status", + }, + { + header: "过期信息", + id: "expire", + cell: ({ row }) => expireLabel(row.original), + }, + { + header: "发放时间", + accessorKey: "issued_at", + // cell: ({ row }) => + // format(new Date(row.original.issued_at), "yyyy-MM-dd HH:mm"), + }, + { + header: "备注", + accessorKey: "remark", + cell: ({ row }) => ( + + {row.original.remark || "-"} + + ), + }, + { + id: "action", + meta: { pin: "right" }, + header: "操作", + cell: ({ row }) => ( +
+ + +
+ ), + }, + ]} + /> +
+
+ ) +} + +function UpdateStatusDialog({ + coupon, + userId, + onSuccess, +}: { + coupon: Coupon + userId: number + onSuccess?: () => void +}) { + const [open, setOpen] = useState(false) + const [selectedStatus, setSelectedStatus] = + useState( + // String(coupon.coupon_status), + ) + const [loading, setLoading] = useState(false) + + const handleConfirm = async () => { + // setLoading(true) + // try { + // const status = Number(selectedStatus) + // const result = await updateUserCouponStatus({ + // user_id: userId, + // coupon_id: coupon.id, + // coupon_status: status, + // }) + // if (result.success) { + // toast.success("状态更新成功") + // onSuccess?.() + // setOpen(false) + // } else { + // toast.error(result.message || "更新失败") + // } + // } catch (error) { + // const message = error instanceof Error ? error.message : "更新失败" + // toast.error(`状态更新失败: ${message}`) + // } finally { + // setLoading(false) + // } + } + + return ( + + + + + + + 修改优惠券状态 + +
+

+ 优惠券: {coupon.name} ({coupon.code}) +

+ +
+ + + + + + +
+
+ ) +} + +function RevokeDialog({ + coupon, + userId, + onSuccess, +}: { + coupon: Coupon + userId: number + onSuccess?: () => void +}) { + const [loading, setLoading] = useState(false) + + const handleConfirm = async () => { + // setLoading(true) + // try { + // const result = await revokeUserCoupon({ + // user_id: userId, + // coupon_id: coupon.id, + // }) + // if (result.success) { + // toast.success("撤销成功") + // onSuccess?.() + // } else { + // toast.error(result.message || "撤销失败") + // } + // } catch (error) { + // const message = error instanceof Error ? error.message : "撤销失败" + // toast.error(`撤销失败: ${message}`) + // } finally { + // setLoading(false) + // } + } + + return ( + + + + + + + 确认撤销 + + 确定要撤销「{coupon.name}」({coupon.code}) + 吗?此操作将标记优惠券为已撤销状态。 + + + + 取消 + + 撤销 + + + + + ) +} diff --git a/src/app/(root)/client/issue/page.tsx b/src/app/(root)/client/issue/page.tsx deleted file mode 100644 index 9ea161e..0000000 --- a/src/app/(root)/client/issue/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -"use client" -import { useRouter, useSearchParams } from "next/navigation" - -export default function IssuePage() { - const router = useRouter() - const searchParams = useSearchParams() - const userId = searchParams.get("userId") - const userPhone = searchParams.get("phone") - return
发放优惠券
-} diff --git a/src/app/(root)/coupon/page.tsx b/src/app/(root)/coupon/page.tsx index 57a47e9..7db953a 100644 --- a/src/app/(root)/coupon/page.tsx +++ b/src/app/(root)/coupon/page.tsx @@ -21,7 +21,7 @@ import { Button } from "@/components/ui/button" import { ScopeCouponWriteAssign } from "@/lib/scopes" import type { Coupon } from "@/models/coupon" import { CreateDiscount } from "./create" -import { IssueCoupon } from "./issue" +import { ReleaseCoupon } from "./release" import { UpdateCoupon } from "./update" export default function CouponPage() { @@ -114,7 +114,7 @@ export default function CouponPage() { onSuccess={table.refresh} /> - diff --git a/src/app/(root)/coupon/issue.tsx b/src/app/(root)/coupon/release.tsx similarity index 98% rename from src/app/(root)/coupon/issue.tsx rename to src/app/(root)/coupon/release.tsx index 10be569..2fe22f7 100644 --- a/src/app/(root)/coupon/issue.tsx +++ b/src/app/(root)/coupon/release.tsx @@ -4,7 +4,7 @@ import { Suspense, useCallback, useState } from "react" import { Controller, useForm } from "react-hook-form" import { toast } from "sonner" import z from "zod" -import { issueCoupon } from "@/actions/coupon" +import { getReleaseCoupon } from "@/actions/coupon" import { getPageUser } from "@/actions/user" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" @@ -33,7 +33,10 @@ const filterSchema = z.object({ }) type FormValues = z.infer -export function IssueCoupon(props: { coupon: Coupon; onSuccess?: () => void }) { +export function ReleaseCoupon(props: { + coupon: Coupon + onSuccess?: () => void +}) { const [open, setOpen] = useState(false) const [userList, setUserList] = useState([]) const [loading, setLoading] = useState(false) @@ -104,7 +107,7 @@ export function IssueCoupon(props: { coupon: Coupon; onSuccess?: () => void }) { return } try { - const result = await issueCoupon({ + const result = await getReleaseCoupon({ coupon_id: coupon.id, user_id: targetUser.id, }) diff --git a/src/app/(root)/cust/page.tsx b/src/app/(root)/cust/page.tsx index 2a78ac6..3c10fae 100644 --- a/src/app/(root)/cust/page.tsx +++ b/src/app/(root)/cust/page.tsx @@ -18,12 +18,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -import { - Field, - FieldError, - FieldGroup, - FieldLabel, -} from "@/components/ui/field" +import { Field, FieldError, FieldLabel } from "@/components/ui/field" import { Input } from "@/components/ui/input" import { Select, @@ -452,7 +447,7 @@ export default function CustPage() { router.push( - `/client/issue?userId=${user.id}&phone=${user.phone}`, + `/client/coupon?userId=${user.id}&phone=${user.phone}`, ) } >