diff --git a/app/models/user.py b/app/models/user.py index 77d65ae..af20174 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -127,6 +127,7 @@ class UserResponse(User): service_ids: list[int] subscription_url: str owner_username: str + traffic_reset_at: datetime | None model_config = ConfigDict(from_attributes=True) diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json index 9f560be..13efaf0 100644 --- a/dashboard/public/locales/en.json +++ b/dashboard/public/locales/en.json @@ -172,6 +172,10 @@ "created_at": "Created At", "no_use": "No Use", "data_limit_reached": "Data Limit Reached", + "sub_updated_at": "Last subscription update", + "sub_last_user_agent": "Last User Agent", + "traffic_reset_at": "Last traffic reset", + "lifetime_used_traffic": "Lifetime Used Traffic", "note-placeholder": "Additional information about user", "data_limit": "Data Limit", "traffic": "Traffic", diff --git a/dashboard/src/features/users/components/dialogs/settings/index.tsx b/dashboard/src/features/users/components/dialogs/settings/index.tsx index 50f7aaf..2d5a13e 100644 --- a/dashboard/src/features/users/components/dialogs/settings/index.tsx +++ b/dashboard/src/features/users/components/dialogs/settings/index.tsx @@ -1,94 +1,81 @@ import { - Button, - ScrollArea, - Tabs, - TabsContent, - TabsList, - TabsTrigger, - Awaiting, - SettingsInfoSkeleton, - SettingsDialogProps, - SettingsDialog, + ScrollArea, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + Awaiting, + SettingsInfoSkeleton, + SettingsDialogProps, + SettingsDialog, } from "@marzneshin/components"; import { - UserServicesTable, - useUserUsageResetCmd, - type UserType, + UserServicesTable, + type UserType, } from "@marzneshin/features/users"; import { type FC } from "react"; import { useTranslation } from "react-i18next"; import { - UserStatusEnableButton, - SubscriptionActions, - QRCodeSection, - UserInfoTable, + SubscriptionActions, + QRCodeSection, + UserInfoTable, } from "./user-info"; interface UsersSettingsDialogProps extends SettingsDialogProps { - entity: UserType | null; - onClose: () => void; - isPending: boolean; + entity: UserType | null; + onClose: () => void; + isPending: boolean; } export const UsersSettingsDialog: FC = ({ - onOpenChange, - open, - entity, - onClose, - isPending, + onOpenChange, + open, + entity, + onClose, + isPending, }) => { - const { t } = useTranslation(); - const { mutate: resetUsage } = useUserUsageResetCmd(); + const { t } = useTranslation(); - return ( - - - - - - {t("user_info")} - - - {t("services")} - - - {t("subscription")} - - - - -
- - -
-
- - - - - - - -
- - ) : ( -
Not Found
- ) - } - Skeleton={} - isFetching={isPending} - /> -
- ); + return ( + + + + + + {t("user_info")} + + + {t("services")} + + + {t("subscription")} + + + + + + + + + + + + + + + ) : ( +
Not Found
+ ) + } + Skeleton={} + isFetching={isPending} + /> +
+ ); }; diff --git a/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx b/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx index fbbc841..aafc29e 100644 --- a/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx +++ b/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx @@ -1,5 +1,6 @@ import { Card, + Button, CardContent, CardHeader, CardTitle, @@ -10,24 +11,41 @@ import { Badge } from "@marzneshin/components"; import type { FC } from "react"; +import { UserStatusEnableButton } from "./user-status-enable-button"; import { type UserProp, UserEnabledPill, UserUsedTraffic, UserActivatedPill, + useUserUsageResetCmd, UserDataLimitReachedPill, UserExpiredPill, } from "@marzneshin/features/users"; import { useTranslation } from "react-i18next"; +import { formatDistanceToNow } from "date-fns"; +import { format } from '@chbphone55/pretty-bytes'; +import { TimerReset } from "lucide-react"; export const UserInfoTable: FC = ({ user: entity }) => { const { t } = useTranslation(); const expireDate = entity.expire_date ? new Date(entity.expire_date) : null; + const { mutate: resetUsage } = useUserUsageResetCmd(); + const lifetimeUsedTrafficByte = format(entity.lifetime_used_traffic); return ( {t("user_info")} +
+ + +
@@ -80,16 +98,23 @@ export const UserInfoTable: FC = ({ user: entity }) => { ), }[entity.expire_strategy] } - } /> + + {entity.online_at ? ( - ) : ( = ({ user: entity }) => { value={{t("page.users.no_use")}} /> )} + + diff --git a/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx b/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx index 189f043..9d8c876 100644 --- a/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx +++ b/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx @@ -1,7 +1,7 @@ import { Button } from "@marzneshin/components"; import { cn } from "@marzneshin/utils"; import { UserType, useUserStatusEnable } from "@marzneshin/features/users"; -import { LoaderIcon } from "lucide-react"; +import { LoaderIcon, UserCheck, UserX } from "lucide-react"; import { FC, useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -20,13 +20,14 @@ export const UserStatusEnableButton: FC = ({ user } setUserStatus(!tempUserStatus) }, [user, userStatus, userStatusEnable]); - const bgColor = isPending ? 'bg-muted-foreground' : (userStatus ? 'bg-red-400' : 'bg-green-400') + const bgColor = isPending ? 'bg-muted-foreground' : (userStatus ? 'bg-destructive' : 'bg-success') return ( ) diff --git a/dashboard/src/features/users/types/user.ts b/dashboard/src/features/users/types/user.ts index a14644f..61848af 100644 --- a/dashboard/src/features/users/types/user.ts +++ b/dashboard/src/features/users/types/user.ts @@ -11,6 +11,7 @@ export interface UserType { lifetime_used_traffic: number; used_traffic: number; sub_updated_at?: Date | string; + traffic_reset_at?: Date | string; sub_last_user_agent?: string; enabled: boolean; activated: boolean;