294 lines
9.0 KiB
TypeScript
294 lines
9.0 KiB
TypeScript
"use client";
|
||
|
||
import Link from "next/link";
|
||
import { useParams } from "next/navigation"; // useRouter حذف شد
|
||
import { useState, useEffect } from "react";
|
||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||
import { API_ROUTES, request } from "@/services/api";
|
||
import {
|
||
Avatar,
|
||
Breadcrumb,
|
||
Button,
|
||
Card,
|
||
Descriptions,
|
||
Divider,
|
||
message as antdMessage,
|
||
Select,
|
||
Spin,
|
||
Table,
|
||
Tabs,
|
||
Tag,
|
||
Typography,
|
||
} from "antd";
|
||
import type { ColumnsType } from "antd/es/table";
|
||
import { UserOutlined, RobotOutlined, TeamOutlined,HistoryOutlined } from "@ant-design/icons";
|
||
// تعریف تایپها
|
||
interface SubUser {
|
||
id: string;
|
||
username: string;
|
||
name: string | null;
|
||
mobile_number: string | null;
|
||
created_at: string;
|
||
level: number;
|
||
}
|
||
|
||
// تعریف تایپ بات برای جلوگیری از خطای any
|
||
interface Bot {
|
||
id: number;
|
||
name: string;
|
||
description: string;
|
||
image: string | null;
|
||
}
|
||
|
||
interface UserDetail {
|
||
id: string;
|
||
username: string;
|
||
name: string | null;
|
||
mobile_number: string | null;
|
||
email: string | null;
|
||
image: string | null;
|
||
level: number;
|
||
credit: number;
|
||
income: number;
|
||
created_at: string;
|
||
sub_users: SubUser[];
|
||
bots?: Bot[]; // استفاده از تایپ Bot به جای any
|
||
}
|
||
|
||
export default function UserPage() {
|
||
const { id } = useParams();
|
||
const [messageApi, contextHolder] = antdMessage.useMessage();
|
||
const [selectedLevel, setSelectedLevel] = useState<number>(1);
|
||
|
||
// 1. دریافت اطلاعات کامل کاربر
|
||
const { data: user, isLoading, refetch } = useQuery({
|
||
queryKey: ["user", id],
|
||
queryFn: async () => {
|
||
const res = await request.get(API_ROUTES.USER.ONE(id as string));
|
||
return res.data.user as UserDetail;
|
||
},
|
||
});
|
||
|
||
// 2. سینک کردن لول انتخابی
|
||
useEffect(() => {
|
||
if (user) {
|
||
setSelectedLevel(user.level || 1);
|
||
}
|
||
}, [user]);
|
||
|
||
// 3. تابع آپدیت لول
|
||
const updateLevelMutation = useMutation({
|
||
mutationFn: async () => {
|
||
if (!user?.id) throw new Error("User ID not found");
|
||
return await request.put(`/admin/user/${user.id}/level`, {
|
||
level: selectedLevel,
|
||
});
|
||
},
|
||
onSuccess: () => {
|
||
messageApi.success("سطح کاربر با موفقیت بروزرسانی شد");
|
||
refetch();
|
||
},
|
||
onError: () => {
|
||
messageApi.error("خطا در تغییر سطح کاربر");
|
||
},
|
||
});
|
||
|
||
// ستونهای جدول زیرمجموعهها
|
||
const subUsersColumns: ColumnsType<SubUser> = [
|
||
{
|
||
title: "نام کاربری",
|
||
dataIndex: "username",
|
||
key: "username",
|
||
render: (text) => (
|
||
<Link href={`/user/${text}`} className="text-blue-600 hover:underline">
|
||
{text}
|
||
</Link>
|
||
),
|
||
},
|
||
{
|
||
title: "نام",
|
||
dataIndex: "name",
|
||
key: "name",
|
||
render: (text) => text || <span className="text-gray-400">---</span>,
|
||
},
|
||
{
|
||
title: "شماره موبایل",
|
||
dataIndex: "mobile_number",
|
||
key: "mobile_number",
|
||
render: (text) => <span dir="ltr">{text}</span>,
|
||
},
|
||
{
|
||
title: "سطح",
|
||
dataIndex: "level",
|
||
key: "level",
|
||
render: (level) => (
|
||
<Tag color={level > 1 ? "gold" : "blue"}>سطح {level}</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: "تاریخ عضویت",
|
||
dataIndex: "created_at",
|
||
key: "created_at",
|
||
render: (date) => new Date(date).toLocaleDateString("fa-IR"),
|
||
},
|
||
];
|
||
|
||
if (isLoading)
|
||
return (
|
||
<div className="flex h-screen w-full items-center justify-center">
|
||
<Spin size="large" />
|
||
</div>
|
||
);
|
||
|
||
if (!user)
|
||
return (
|
||
<div className="flex h-screen w-full items-center justify-center text-red-500">
|
||
کاربر یافت نشد
|
||
</div>
|
||
);
|
||
|
||
const items = [
|
||
{
|
||
key: "1",
|
||
label: (
|
||
<span>
|
||
<RobotOutlined />
|
||
دستیارها
|
||
</span>
|
||
),
|
||
children: (
|
||
<div className="p-4">
|
||
{user.bots && user.bots.length > 0 ? (
|
||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
{user.bots.map((bot: Bot) => (
|
||
<Card key={bot.id} title={bot.name} size="small" extra={<Link href={`/assistant/${bot.id}`}>مشاهده</Link>}>
|
||
<p className="truncate text-gray-500">{bot.description}</p>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-gray-500 text-center">این کاربر دستیاری ندارد.</p>
|
||
)}
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
key: "2",
|
||
label: (
|
||
<span>
|
||
<TeamOutlined />
|
||
زیرمجموعهها ({user.sub_users ? user.sub_users.length : 0})
|
||
</span>
|
||
),
|
||
children: (
|
||
<Table
|
||
columns={subUsersColumns}
|
||
dataSource={user.sub_users || []}
|
||
rowKey="id"
|
||
pagination={{ pageSize: 5 }}
|
||
locale={{ emptyText: "زیرمجموعهای یافت نشد" }}
|
||
/>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<>
|
||
{contextHolder}
|
||
|
||
<Breadcrumb
|
||
items={[
|
||
{ title: <Link href="/dashboard">داشبورد</Link> },
|
||
{ title: <Link href="/user">کاربران</Link> },
|
||
{ title: user.username },
|
||
]}
|
||
className="mb-6"
|
||
/>
|
||
|
||
<div className="flex flex-col gap-6">
|
||
<Card bordered={false} className="shadow-sm">
|
||
<div className="flex flex-col md:flex-row gap-6">
|
||
<div className="flex flex-col items-center gap-4 border-l pl-6 md:w-1/4">
|
||
<Avatar
|
||
size={100}
|
||
src={
|
||
user.image
|
||
? `${process.env.NEXT_PUBLIC_API_BASE_URL}${user.image}`
|
||
: `${process.env.NEXT_PUBLIC_AVATAR_BASE_URL}?seed=${user.username}`
|
||
}
|
||
icon={<UserOutlined />}
|
||
/>
|
||
<div className="text-center">
|
||
<Typography.Title level={4} className="!mb-0">
|
||
{user.name || "کاربر ناشناس"}
|
||
</Typography.Title>
|
||
<Typography.Text type="secondary" copyable>@{user.username}</Typography.Text>
|
||
</div>
|
||
|
||
<Tag color="purple" className="px-3 py-1 text-sm mt-2">
|
||
سطح فعلی: {user.level}
|
||
</Tag>
|
||
|
||
{/* ۲. دکمه جدید برای مشاهده تاریخچه تراکنشها */}
|
||
<Link href={`/user/${user.id}/history`} className="w-full">
|
||
<Button
|
||
block
|
||
icon={<HistoryOutlined />}
|
||
className="mt-2 text-blue-600 border-blue-200 bg-blue-50"
|
||
>
|
||
تاریخچه تراکنشها
|
||
</Button>
|
||
</Link>
|
||
</div>
|
||
|
||
<div className="flex-1">
|
||
<Descriptions title="مشخصات فردی" layout="vertical" column={{ xs: 1, sm: 2, md: 3 }}>
|
||
<Descriptions.Item label="ایمیل">{user.email || "---"}</Descriptions.Item>
|
||
<Descriptions.Item label="شماره موبایل">{user.mobile_number || "---"}</Descriptions.Item>
|
||
<Descriptions.Item label="تاریخ ثبت نام">
|
||
{new Date(user.created_at).toLocaleDateString("fa-IR")}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="موجودی حساب">
|
||
{user.credit.toLocaleString()} سکه
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="درآمد">
|
||
{user.income.toLocaleString()} تومان
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
|
||
<Divider />
|
||
|
||
<div className="flex items-end gap-4 bg-blue-50 p-4 rounded-lg border border-blue-100">
|
||
<div className="w-full md:w-1/3">
|
||
<label className="block text-xs text-gray-500 mb-1">تعیین سطح جدید</label>
|
||
<Select
|
||
className="w-full"
|
||
value={selectedLevel}
|
||
onChange={(val) => setSelectedLevel(val)}
|
||
options={[
|
||
{ label: "بالای 30 سال", value: 1 },
|
||
{ label: "بین 18 تا 30 سال", value: 2 },
|
||
{ label: "زیر 18 سال", value: 3 },
|
||
]}
|
||
/>
|
||
</div>
|
||
<Button
|
||
type="primary"
|
||
onClick={() => updateLevelMutation.mutate()}
|
||
loading={updateLevelMutation.isPending}
|
||
disabled={selectedLevel === user.level}
|
||
>
|
||
بروزرسانی سطح
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<Card bordered={false} className="shadow-sm">
|
||
<Tabs defaultActiveKey="2" items={items} />
|
||
</Card>
|
||
</div>
|
||
</>
|
||
);
|
||
} |