♻️ refactor: Add localization updates for log and server files across multiple languages

- Added new keys for "server", "subscribe", "detail", "pending", "sending", "sent", and "unknown" in log.json files for various languages.
- Introduced a "type" object with transaction types (Recharge, Withdraw, Purchase, Refund, Reward, Commission) in log.json files for multiple languages.
- Updated "traffic_ratio" to "Ratio" and added "transport" in servers.json for English localization.
- Ensured consistency and accuracy in translations for all affected languages including German, English, Spanish, French, Russian, Chinese, and more.
This commit is contained in:
web
2025-09-05 04:48:10 -07:00
parent 351fffcc78
commit 2bcd4cf30c
41 changed files with 849 additions and 150 deletions
+34 -8
View File
@@ -1,6 +1,8 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { Display } from '@/components/display';
import { OrderLink } from '@/components/order-link';
import { ProTable } from '@/components/pro-table';
import { filterBalanceLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
@@ -10,9 +12,19 @@ import { useSearchParams } from 'next/navigation';
export default function BalanceLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const getBalanceTypeText = (type: number) => {
const typeText = t(`type.${type}`);
if (typeText === `log.type.${type}`) {
return `${t('unknown')} (${type})`;
}
return typeText;
};
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
};
return (
@@ -25,10 +37,26 @@ export default function BalanceLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'amount', header: t('column.amount') },
{ accessorKey: 'order_id', header: t('column.orderId') },
{ accessorKey: 'balance', header: t('column.balance') },
{ accessorKey: 'type', header: t('column.type') },
{
accessorKey: 'amount',
header: t('column.amount'),
cell: ({ row }) => <Display type='currency' value={row.original.amount} />,
},
{
accessorKey: 'order_id',
header: t('column.orderId'),
cell: ({ row }) => <OrderLink orderId={row.original.order_id} />,
},
{
accessorKey: 'balance',
header: t('column.balance'),
cell: ({ row }) => <Display type='currency' value={row.original.balance} />,
},
{
accessorKey: 'type',
header: t('column.type'),
cell: ({ row }) => getBalanceTypeText(row.original.type),
},
{
accessorKey: 'timestamp',
header: t('column.time'),
@@ -36,7 +64,6 @@ export default function BalanceLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
@@ -44,7 +71,6 @@ export default function BalanceLogPage() {
const { data } = await filterBalanceLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
@@ -1,6 +1,8 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { Display } from '@/components/display';
import { OrderLink } from '@/components/order-link';
import { ProTable } from '@/components/pro-table';
import { filterCommissionLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
@@ -10,9 +12,19 @@ import { useSearchParams } from 'next/navigation';
export default function CommissionLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const getCommissionTypeText = (type: number) => {
const typeText = t(`type.${type}`);
if (typeText === `log.type.${type}`) {
return `${t('unknown')} (${type})`;
}
return typeText;
};
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
};
return (
@@ -25,9 +37,21 @@ export default function CommissionLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'amount', header: t('column.amount') },
{ accessorKey: 'order_no', header: t('column.orderNo') },
{ accessorKey: 'type', header: t('column.type') },
{
accessorKey: 'amount',
header: t('column.amount'),
cell: ({ row }) => <Display type='currency' value={row.original.amount} />,
},
{
accessorKey: 'order_no',
header: t('column.orderNo'),
cell: ({ row }) => <OrderLink orderId={row.original.order_no} />,
},
{
accessorKey: 'type',
header: t('column.type'),
cell: ({ row }) => getCommissionTypeText(row.original.type),
},
{
accessorKey: 'timestamp',
header: t('column.time'),
@@ -35,7 +59,6 @@ export default function CommissionLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
@@ -43,7 +66,6 @@ export default function CommissionLogPage() {
const { data } = await filterCommissionLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
+30 -6
View File
@@ -10,9 +10,12 @@ import { useSearchParams } from 'next/navigation';
export default function EmailLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
};
return (
<ProTable<API.MessageLog, { search?: string }>
@@ -20,11 +23,10 @@ export default function EmailLogPage() {
initialFilters={initialFilters}
columns={[
{
accessorKey: 'id',
header: t('column.id'),
cell: ({ row }) => <Badge>{row.getValue('id')}</Badge>,
accessorKey: 'platform',
header: t('column.platform'),
cell: ({ row }) => <Badge>{row.getValue('platform')}</Badge>,
},
{ accessorKey: 'platform', header: t('column.platform') },
{ accessorKey: 'to', header: t('column.to') },
{ accessorKey: 'subject', header: t('column.subject') },
{
@@ -36,7 +38,29 @@ export default function EmailLogPage() {
</pre>
),
},
{ accessorKey: 'status', header: t('column.status') },
{
accessorKey: 'status',
header: t('column.status'),
cell: ({ row }) => {
const status = row.original.status;
const getStatusVariant = (status: any) => {
if (status === 1) {
return 'default';
} else if (status === 0) {
return 'destructive';
}
return 'outline';
};
const getStatusText = (status: any) => {
if (status === 1) return t('sent');
if (status === 0) return t('failed');
return t('unknown');
};
return <Badge variant={getStatusVariant(status)}>{getStatusText(status)}</Badge>;
},
},
{
accessorKey: 'created_at',
header: t('column.time'),
+30 -9
View File
@@ -1,6 +1,8 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { Display } from '@/components/display';
import { OrderLink } from '@/components/order-link';
import { ProTable } from '@/components/pro-table';
import { filterGiftLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
@@ -10,9 +12,12 @@ import { useSearchParams } from 'next/navigation';
export default function GiftLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
// 获取今日日期作为默认值
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
};
return (
@@ -25,10 +30,28 @@ export default function GiftLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
{ accessorKey: 'order_no', header: t('column.orderNo') },
{ accessorKey: 'amount', header: t('column.amount') },
{ accessorKey: 'balance', header: t('column.balance') },
{
accessorKey: 'subscribe_id',
header: t('column.subscribe'),
cell: ({ row }) => (
<UserSubscribeDetail id={Number(row.original.subscribe_id)} enabled hoverCard />
),
},
{
accessorKey: 'order_no',
header: t('column.orderNo'),
cell: ({ row }) => <OrderLink orderId={row.original.order_no} />,
},
{
accessorKey: 'amount',
header: t('column.amount'),
cell: ({ row }) => <Display type='currency' value={row.original.amount} />,
},
{
accessorKey: 'balance',
header: t('column.balance'),
cell: ({ row }) => <Display type='currency' value={row.original.balance} />,
},
{ accessorKey: 'remark', header: t('column.remark') },
{
accessorKey: 'timestamp',
@@ -37,7 +60,6 @@ export default function GiftLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
@@ -45,7 +67,6 @@ export default function GiftLogPage() {
const { data } = await filterGiftLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
+35 -7
View File
@@ -6,19 +6,27 @@ import { ProTable } from '@/components/pro-table';
import { filterLoginLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
import { Badge } from '@workspace/ui/components/badge';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@workspace/ui/components/tooltip';
import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation';
export default function LoginLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
};
return (
<ProTable<API.LoginLog, { search?: string }>
<ProTable<API.LoginLog, { date?: string; user_id?: number }>
header={{ title: t('title.login') }}
initialFilters={initialFilters}
columns={[
@@ -27,13 +35,35 @@ export default function LoginLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'method', header: t('column.method') },
{
accessorKey: 'method',
header: t('column.method'),
cell: ({ row }) => <span className='capitalize'>{row.original.method}</span>,
},
{
accessorKey: 'login_ip',
header: t('column.ip'),
cell: ({ row }) => <IpLink ip={String((row.original as any).login_ip || '')} />,
},
{ accessorKey: 'user_agent', header: t('column.userAgent') },
{
accessorKey: 'user_agent',
header: t('column.userAgent'),
cell: ({ row }) => {
const userAgent = String(row.original.user_agent || '');
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className='max-w-48 cursor-help truncate'>{userAgent}</div>
</TooltipTrigger>
<TooltipContent>
<p className='max-w-md break-words'>{userAgent}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
},
{
accessorKey: 'success',
header: t('column.success'),
@@ -50,7 +80,6 @@ export default function LoginLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
@@ -58,7 +87,6 @@ export default function LoginLogPage() {
const { data } = await filterLoginLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
+30 -6
View File
@@ -9,9 +9,12 @@ import { useSearchParams } from 'next/navigation';
export default function MobileLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
};
return (
<ProTable<API.MessageLog, { search?: string }>
@@ -19,11 +22,10 @@ export default function MobileLogPage() {
initialFilters={initialFilters}
columns={[
{
accessorKey: 'id',
header: t('column.id'),
cell: ({ row }) => <Badge>{row.getValue('id')}</Badge>,
accessorKey: 'platform',
header: t('column.platform'),
cell: ({ row }) => <Badge>{row.getValue('platform')}</Badge>,
},
{ accessorKey: 'platform', header: t('column.platform') },
{ accessorKey: 'to', header: t('column.to') },
{ accessorKey: 'subject', header: t('column.subject') },
{
@@ -35,7 +37,29 @@ export default function MobileLogPage() {
</pre>
),
},
{ accessorKey: 'status', header: t('column.status') },
{
accessorKey: 'status',
header: t('column.status'),
cell: ({ row }) => {
const status = row.original.status;
const getStatusVariant = (status: any) => {
if (status === 1) {
return 'default';
} else if (status === 0) {
return 'destructive';
}
return 'outline';
};
const getStatusText = (status: any) => {
if (status === 1) return t('sent');
if (status === 0) return t('failed');
return t('unknown');
};
return <Badge variant={getStatusVariant(status)}>{getStatusText(status)}</Badge>;
},
},
{
accessorKey: 'created_at',
header: t('column.time'),
+35 -7
View File
@@ -5,19 +5,27 @@ import { IpLink } from '@/components/ip-link';
import { ProTable } from '@/components/pro-table';
import { filterRegisterLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@workspace/ui/components/tooltip';
import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation';
export default function RegisterLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
};
return (
<ProTable<API.RegisterLog, { search?: string }>
<ProTable<API.RegisterLog, { date?: string; user_id?: number }>
header={{ title: t('title.register') }}
initialFilters={initialFilters}
columns={[
@@ -26,14 +34,36 @@ export default function RegisterLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'auth_method', header: t('column.method') },
{
accessorKey: 'auth_method',
header: t('column.method'),
cell: ({ row }) => <span className='capitalize'>{row.original.auth_method}</span>,
},
{ accessorKey: 'identifier', header: t('column.identifier') },
{
accessorKey: 'register_ip',
header: t('column.ip'),
cell: ({ row }) => <IpLink ip={String((row.original as any).register_ip || '')} />,
},
{ accessorKey: 'user_agent', header: t('column.userAgent') },
{
accessorKey: 'user_agent',
header: t('column.userAgent'),
cell: ({ row }) => {
const userAgent = String(row.original.user_agent || '');
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className='max-w-48 cursor-help truncate'>{userAgent}</div>
</TooltipTrigger>
<TooltipContent>
<p className='max-w-md break-words'>{userAgent}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
},
{
accessorKey: 'timestamp',
header: t('column.time'),
@@ -41,7 +71,6 @@ export default function RegisterLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
@@ -49,7 +78,6 @@ export default function RegisterLogPage() {
const { data } = await filterRegisterLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
@@ -1,6 +1,7 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { OrderLink } from '@/components/order-link';
import { ProTable } from '@/components/pro-table';
import { filterResetSubscribeLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
@@ -10,15 +11,17 @@ import { useSearchParams } from 'next/navigation';
export default function ResetSubscribeLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_subscribe_id: sp.get('user_subscribe_id')
? Number(sp.get('user_subscribe_id'))
: undefined,
};
return (
<ProTable<API.ResetSubscribeLog, { search?: string }>
<ProTable<API.ResetSubscribeLog, { date?: string; user_subscribe_id?: number }>
header={{ title: t('title.resetSubscribe') }}
initialFilters={initialFilters}
columns={[
@@ -27,9 +30,19 @@ export default function ResetSubscribeLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'user_subscribe_id', header: t('column.subscribeId') },
{
accessorKey: 'user_subscribe_id',
header: t('column.subscribeId'),
cell: ({ row }) => (
<UserSubscribeDetail id={Number(row.original.user_subscribe_id)} enabled hoverCard />
),
},
{ accessorKey: 'type', header: t('column.type') },
{ accessorKey: 'order_no', header: t('column.orderNo') },
{
accessorKey: 'order_no',
header: t('column.orderNo'),
cell: ({ row }) => <OrderLink orderId={row.original.order_no} />,
},
{
accessorKey: 'timestamp',
header: t('column.time'),
@@ -37,7 +50,6 @@ export default function ResetSubscribeLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_subscribe_id', placeholder: t('column.subscribeId') },
]}
@@ -45,7 +57,6 @@ export default function ResetSubscribeLogPage() {
const { data } = await filterResetSubscribeLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_subscribe_id: (filter as any)?.user_subscribe_id,
});
@@ -2,24 +2,60 @@
import { ProTable } from '@/components/pro-table';
import { filterServerTrafficLog } from '@/services/admin/log';
import { filterServerList } from '@/services/admin/server';
import { useQuery } from '@tanstack/react-query';
import { Button } from '@workspace/ui/components/button';
import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
export default function ServerTrafficLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const { data: servers = [] } = useQuery({
queryKey: ['filterServerListAll'],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 999999999 });
return data?.data?.list || [];
},
});
const getServerName = (id?: number) =>
id ? (servers.find((s) => s.id === id)?.name ?? `Server ${id}`) : 'Unknown';
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
server_id: sp.get('server_id') ? Number(sp.get('server_id')) : undefined,
};
return (
<ProTable<API.ServerTrafficLog, { search?: string }>
<ProTable<API.ServerTrafficLog, { date?: string; server_id?: number }>
header={{ title: t('title.serverTraffic') }}
initialFilters={initialFilters}
actions={{
render: (row) => [
<Button key='detail' asChild>
<Link
href={`/dashboard/log/traffic-details?date=${row.date}&server_id=${row.server_id}`}
>
{t('detail')}
</Link>
</Button>,
],
}}
columns={[
{ accessorKey: 'server_id', header: t('column.serverId') },
{
accessorKey: 'server_id',
header: t('column.server'),
cell: ({ row }) => (
<span>
{getServerName(row.original.server_id)} ({row.original.server_id})
</span>
),
},
{
accessorKey: 'upload',
header: t('column.upload'),
@@ -38,7 +74,6 @@ export default function ServerTrafficLogPage() {
{ accessorKey: 'date', header: t('column.date') },
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'server_id', placeholder: t('column.serverId') },
]}
@@ -46,7 +81,6 @@ export default function ServerTrafficLogPage() {
const { data } = await filterServerTrafficLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
server_id: (filter as any)?.server_id,
});
@@ -1,34 +1,59 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterUserSubscribeTrafficLog } from '@/services/admin/log';
import { Button } from '@workspace/ui/components/button';
import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
export default function SubscribeTrafficLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
// 获取今日日期作为默认值
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
user_subscribe_id: sp.get('user_subscribe_id')
? Number(sp.get('user_subscribe_id'))
: undefined,
};
return (
<ProTable<API.UserSubscribeTrafficLog, { search?: string }>
<ProTable<
API.UserSubscribeTrafficLog,
{ date?: string; user_id?: number; user_subscribe_id?: number }
>
header={{ title: t('title.subscribeTraffic') }}
initialFilters={initialFilters}
actions={{
render: (row) => [
<Button key='detail' asChild>
<Link
href={`/dashboard/log/traffic-details?date=${row.date}&user_id=${row.user_id}&subscribe_id=${row.subscribe_id}`}
>
{t('detail')}
</Link>
</Button>,
],
}}
columns={[
{
accessorKey: 'user',
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
{
accessorKey: 'subscribe_id',
header: t('column.subscribe'),
cell: ({ row }) => (
<UserSubscribeDetail id={Number(row.original.subscribe_id)} enabled hoverCard />
),
},
{
accessorKey: 'upload',
header: t('column.upload'),
@@ -50,7 +75,6 @@ export default function SubscribeTrafficLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
{ key: 'user_subscribe_id', placeholder: t('column.subscribeId') },
@@ -59,7 +83,6 @@ export default function SubscribeTrafficLogPage() {
const { data } = await filterUserSubscribeTrafficLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
user_subscribe_id: (filter as any)?.user_subscribe_id,
@@ -1,23 +1,31 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { IpLink } from '@/components/ip-link';
import { ProTable } from '@/components/pro-table';
import { filterSubscribeLog } from '@/services/admin/log';
import { formatDate } from '@/utils/common';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@workspace/ui/components/tooltip';
import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation';
export default function SubscribeLogPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
};
return (
<ProTable<API.SubscribeLog, { search?: string }>
<ProTable<API.SubscribeLog, { date?: string; user_id?: number }>
header={{ title: t('title.subscribe') }}
initialFilters={initialFilters}
columns={[
@@ -26,14 +34,37 @@ export default function SubscribeLogPage() {
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'user_subscribe_id', header: t('column.subscribeId') },
{ accessorKey: 'token', header: t('column.token') },
{
accessorKey: 'user_subscribe_id',
header: t('column.subscribe'),
cell: ({ row }) => (
<UserSubscribeDetail id={Number(row.original.user_subscribe_id)} enabled hoverCard />
),
},
{
accessorKey: 'client_ip',
header: t('column.ip'),
cell: ({ row }) => <IpLink ip={String((row.original as any).client_ip || '')} />,
},
{ accessorKey: 'user_agent', header: t('column.userAgent') },
{
accessorKey: 'user_agent',
header: t('column.userAgent'),
cell: ({ row }) => {
const userAgent = String(row.original.user_agent || '');
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className='max-w-48 cursor-help truncate'>{userAgent}</div>
</TooltipTrigger>
<TooltipContent>
<p className='max-w-md break-words'>{userAgent}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
},
},
{
accessorKey: 'timestamp',
header: t('column.time'),
@@ -41,7 +72,6 @@ export default function SubscribeLogPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'user_id', placeholder: t('column.userId') },
]}
@@ -49,7 +79,6 @@ export default function SubscribeLogPage() {
const { data } = await filterSubscribeLog({
page: pagination.page,
size: pagination.size,
search: filter?.search,
date: (filter as any)?.date,
user_id: (filter as any)?.user_id,
});
@@ -1,9 +1,11 @@
'use client';
import { UserDetail } from '@/app/dashboard/user/user-detail';
import { UserDetail, UserSubscribeDetail } from '@/app/dashboard/user/user-detail';
import { ProTable } from '@/components/pro-table';
import { filterTrafficLogDetails } from '@/services/admin/log';
import { filterServerList } from '@/services/admin/server';
import { formatDate } from '@/utils/common';
import { useQuery } from '@tanstack/react-query';
import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import { useSearchParams } from 'next/navigation';
@@ -11,9 +13,22 @@ import { useSearchParams } from 'next/navigation';
export default function TrafficDetailsPage() {
const t = useTranslations('log');
const sp = useSearchParams();
const today = new Date().toISOString().split('T')[0];
const { data: servers = [] } = useQuery({
queryKey: ['filterServerListAll'],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 999999999 });
return data?.data?.list || [];
},
});
const getServerName = (id?: number) =>
id ? (servers.find((s) => s.id === id)?.name ?? `Server ${id}`) : 'Unknown';
const initialFilters = {
search: sp.get('search') || undefined,
date: sp.get('date') || undefined,
date: sp.get('date') || today,
server_id: sp.get('server_id') ? Number(sp.get('server_id')) : undefined,
user_id: sp.get('user_id') ? Number(sp.get('user_id')) : undefined,
subscribe_id: sp.get('subscribe_id') ? Number(sp.get('subscribe_id')) : undefined,
@@ -23,13 +38,27 @@ export default function TrafficDetailsPage() {
header={{ title: t('title.trafficDetails') }}
initialFilters={initialFilters}
columns={[
{ accessorKey: 'server_id', header: t('column.serverId') },
{
accessorKey: 'server_id',
header: t('column.server'),
cell: ({ row }) => (
<span>
{getServerName(row.original.server_id)} ({row.original.server_id})
</span>
),
},
{
accessorKey: 'user_id',
header: t('column.userId'),
header: t('column.user'),
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
},
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
{
accessorKey: 'subscribe_id',
header: t('column.subscribe'),
cell: ({ row }) => (
<UserSubscribeDetail id={Number(row.original.subscribe_id)} enabled hoverCard />
),
},
{
accessorKey: 'upload',
header: t('column.upload'),
@@ -47,7 +76,6 @@ export default function TrafficDetailsPage() {
},
]}
params={[
{ key: 'search' },
{ key: 'date', type: 'date' },
{ key: 'server_id', placeholder: t('column.serverId') },
{ key: 'user_id', placeholder: t('column.userId') },
@@ -57,7 +85,6 @@ export default function TrafficDetailsPage() {
const { data } = await filterTrafficLogDetails({
page: pagination.page,
size: pagination.size,
search: (filter as any)?.search,
date: (filter as any)?.date,
server_id: (filter as any)?.server_id,
user_id: (filter as any)?.user_id,
@@ -90,6 +90,7 @@ export default function NodeForm(props: {
const serverId = form.watch('server_id');
const { data } = useQuery({
enabled: open,
queryKey: ['filterServerListAll'],
queryFn: async () => {
const { data } = await filterServerList({ page: 1, size: 999999999 });
@@ -99,6 +100,7 @@ export default function NodeForm(props: {
const servers: ServerRow[] = data as ServerRow[];
const { data: tagsData } = useQuery({
enabled: open,
queryKey: ['queryNodeTag'],
queryFn: async () => {
const { data } = await queryNodeTag();
+13 -2
View File
@@ -13,7 +13,6 @@ import { Button } from '@workspace/ui/components/button';
import {
Drawer,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
@@ -168,10 +167,22 @@ export default function Page() {
<DrawerContent className='container mx-auto h-screen *:select-text'>
<DrawerHeader className='border-b text-left'>
<DrawerTitle>{ticket?.title}</DrawerTitle>
<DrawerDescription>{ticket?.description}</DrawerDescription>
</DrawerHeader>
<ScrollArea className='h-full overflow-hidden' ref={scrollRef}>
<div className='flex h-full flex-col gap-4 p-4'>
{/* 显示工单描述作为第一条用户消息 */}
{ticket?.description && (
<div className='flex items-center gap-4'>
<div className='flex flex-col gap-1'>
<p className='text-muted-foreground text-sm'>{formatDate(ticket.created_at)}</p>
<p className='bg-accent w-fit rounded-lg p-2 font-medium'>
{ticket.description}
</p>
</div>
</div>
)}
{/* 显示后续跟进消息 */}
{ticket?.follow?.map((item) => (
<div
key={item.id}
+61 -36
View File
@@ -10,7 +10,15 @@ import { formatBytes } from '@workspace/ui/utils';
import { useTranslations } from 'next-intl';
import Link from 'next/link';
export function UserSubscribeDetail({ id, enabled }: { id: number; enabled: boolean }) {
export function UserSubscribeDetail({
id,
enabled,
hoverCard = false,
}: {
id: number;
enabled: boolean;
hoverCard?: boolean;
}) {
const t = useTranslations('user');
const { data } = useQuery({
@@ -27,7 +35,7 @@ export function UserSubscribeDetail({ id, enabled }: { id: number; enabled: bool
const usedTraffic = data ? data.upload + data.download : 0;
const totalTraffic = data?.traffic || 0;
return (
const subscribeContent = (
<div className='space-y-4'>
<div>
<h3 className='mb-2 text-sm font-medium'>{t('subscriptionInfo')}</h3>
@@ -69,42 +77,59 @@ export function UserSubscribeDetail({ id, enabled }: { id: number; enabled: bool
</div>
</div>
<div>
<h3 className='mb-2 text-sm font-medium'>
{t('userInfo')}
{/* Removed link to legacy user detail page */}
</h3>
<ul className='grid gap-3'>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('userId')}</span>
<span>{data?.user_id}</span>
</li>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('balance')}</span>
<span>
<Display type='currency' value={data?.user.balance} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('giftAmount')}</span>
<span>
<Display type='currency' value={data?.user?.gift_amount} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('commission')}</span>
<span>
<Display type='currency' value={data?.user?.commission} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('createdAt')}</span>
<span>{data?.user?.created_at && formatDate(data?.user?.created_at)}</span>
</li>
</ul>
</div>
{!hoverCard && (
<div>
<h3 className='mb-2 text-sm font-medium'>
{t('userInfo')}
{/* Removed link to legacy user detail page */}
</h3>
<ul className='grid gap-3'>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('userId')}</span>
<span>{data?.user_id}</span>
</li>
<li className='flex items-center justify-between font-semibold'>
<span className='text-muted-foreground'>{t('balance')}</span>
<span>
<Display type='currency' value={data?.user.balance} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('giftAmount')}</span>
<span>
<Display type='currency' value={data?.user?.gift_amount} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('commission')}</span>
<span>
<Display type='currency' value={data?.user?.commission} />
</span>
</li>
<li className='flex items-center justify-between'>
<span className='text-muted-foreground'>{t('createdAt')}</span>
<span>{data?.user?.created_at && formatDate(data?.user?.created_at)}</span>
</li>
</ul>
</div>
)}
</div>
);
if (hoverCard) {
return (
<HoverCard>
<HoverCardTrigger asChild>
<Button variant='link' className='p-0'>
{data?.subscribe?.name || t('loading')}
</Button>
</HoverCardTrigger>
<HoverCardContent className='w-96'>{subscribeContent}</HoverCardContent>
</HoverCard>
);
}
return subscribeContent;
}
export function UserDetail({ id }: { id: number }) {