add user information modal window
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "no-copy-admin-panel-frontend",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 2996",
|
||||
|
||||
@@ -28,6 +28,43 @@ export async function fetchUsesData(page: number, size: number, sortBy?: string,
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const parsed = await response.json();
|
||||
|
||||
if (parsed.message_code === 0) {
|
||||
return parsed.message_body.message_body;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new Error(`${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchUsesInfo(userInfo: number) {
|
||||
console.log('fetchUsesInfo');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/api/admin/get-users`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
version: 1,
|
||||
msg_id: 30014,
|
||||
message_body: {
|
||||
action: 'getIngoById',
|
||||
user_id: userInfo
|
||||
}
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const parsed = await response.json();
|
||||
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
color: v.$p-color;
|
||||
color: var(--primary-color);
|
||||
background-color: v.$white;
|
||||
border: 2px solid v.$p-color;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-radius: 0.375rem;
|
||||
box-shadow: 0 1px 2px 0 v.$shadow-1;
|
||||
transition: background-color 0.2s;
|
||||
@@ -56,7 +56,7 @@
|
||||
margin-top: 0.5rem;
|
||||
transform-origin: top right;
|
||||
background-color: v.$white;
|
||||
border: 2px solid v.$p-color;
|
||||
border: 2px solid var(--primary-color);
|
||||
border-radius: 0.375rem;
|
||||
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||
@@ -74,7 +74,7 @@
|
||||
font-size: 0.875rem;
|
||||
text-align: left;
|
||||
transition: background-color 0.2s ease;
|
||||
color: v.$p-color;
|
||||
color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
background-color: #dbeafe;
|
||||
@@ -128,7 +128,7 @@
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
z-index: 100;
|
||||
|
||||
.admin-nav {
|
||||
padding: 16px 0;
|
||||
@@ -807,7 +807,7 @@
|
||||
border: 2px solid v.$border-color-1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 2px #0000000d;
|
||||
color: v.$p-color;
|
||||
color: var(--primary-color);
|
||||
|
||||
.icon {
|
||||
width: 18px;
|
||||
@@ -834,7 +834,7 @@
|
||||
border: 2px solid v.$border-color-1;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
color: v.$p-color;
|
||||
color: var(--primary-color);
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
@@ -842,7 +842,7 @@
|
||||
}
|
||||
|
||||
&.current {
|
||||
background-color: v.$p-color;
|
||||
background-color: var(--primary-color);
|
||||
color: v.$white;
|
||||
}
|
||||
|
||||
@@ -1230,7 +1230,7 @@
|
||||
border: 2px solid v.$border-color-1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 1px 2px #0000000d;
|
||||
color: v.$p-color;
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
@@ -1256,7 +1256,7 @@
|
||||
border: 2px solid v.$border-color-1;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 10px;
|
||||
color: v.$p-color;
|
||||
color: var(--primary-color);
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
@@ -1265,7 +1265,7 @@
|
||||
}
|
||||
|
||||
&.current {
|
||||
background-color: v.$p-color;
|
||||
background-color: var(--primary-color);
|
||||
color: v.$white;
|
||||
}
|
||||
|
||||
@@ -1276,4 +1276,155 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-wrapper {
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: v.$bg-darkening;
|
||||
|
||||
.modal-window {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
padding: 20px;
|
||||
background: v.$white;
|
||||
color: v.$text-p;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 4px 20px v.$shadow-1;
|
||||
|
||||
&:has(.modal-window-view-user) {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-window-view-user {
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
|
||||
&-content {
|
||||
padding: 10px;
|
||||
max-height: calc(90vh - 80px);
|
||||
overflow-y: auto;
|
||||
background: v.$white;
|
||||
|
||||
&-wrapper {
|
||||
height: 100%;
|
||||
max-height: 80vh;
|
||||
|
||||
&.image {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.image-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 400px;
|
||||
background: #fafafa;
|
||||
border-radius: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.image-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
|
||||
.user-info-wrapper {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
@media (max-width: 875px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.user-info-card {
|
||||
background: #ffffff;
|
||||
border: 1px solid v.$border-color-1;
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
min-width: 400px;
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: 470px) {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.info-header {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid v.$border-color-1;
|
||||
|
||||
h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 6px 10px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
width: 100%;
|
||||
|
||||
&.image {
|
||||
text-align: center;
|
||||
max-width: 200px;
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
text-align: left;
|
||||
word-break: break-word;
|
||||
/* max-width: 150px; */
|
||||
}
|
||||
|
||||
.status-badges {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,8 @@ import { toast } from 'sonner';
|
||||
import { useUsersData } from '@/app/hooks/react-query/useUsersData';
|
||||
import { TransformedUser } from '@/app/hooks/react-query/useUsersData';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { fetchUsesInfo } from '@/app/actions/usersActions';
|
||||
import { UserInfoModalWindow } from '@/app/ui/users/user-info-modal-window';
|
||||
|
||||
// Форматирование даты из timestamp
|
||||
const formatDate = (timestamp: number) => {
|
||||
@@ -395,7 +397,7 @@ export default function TanstakUsersTable() {
|
||||
<div className="actions">
|
||||
<div className="actions-group">
|
||||
<button
|
||||
onClick={() => handleView(row.original)}
|
||||
onClick={() => handleView(row.original.id)}
|
||||
className="bg-blue-500 hover:bg-blue-600"
|
||||
>
|
||||
<IconEye />
|
||||
@@ -410,9 +412,16 @@ export default function TanstakUsersTable() {
|
||||
[]
|
||||
);
|
||||
|
||||
// Обработчики действий
|
||||
const handleView = (file: TransformedUser) => {
|
||||
console.log(`Просмотр файла: ${file.id}`);
|
||||
const handleView = async (useId: number) => {
|
||||
let userInfo = await fetchUsesInfo(useId);
|
||||
console.log('userInfo');
|
||||
|
||||
setOpenWindowChildren(() => {
|
||||
return (
|
||||
<UserInfoModalWindow userInfo={userInfo} setWindowClose={setOpenWindow} setWindowChildren={setOpenWindowChildren} />
|
||||
)
|
||||
})
|
||||
setOpenWindow(true);
|
||||
};
|
||||
|
||||
// Фильтрация по типу файла и дате
|
||||
@@ -478,7 +487,9 @@ export default function TanstakUsersTable() {
|
||||
|
||||
return (
|
||||
<div className="tanstak-table-wrapper">
|
||||
<ModalWindow children={openWindowChildren} state={openWindow} callBack={setOpenWindow}></ModalWindow>
|
||||
<ModalWindow state={openWindow} callBack={setOpenWindow}>
|
||||
{openWindowChildren}
|
||||
</ModalWindow>
|
||||
{/* Фильтры */}
|
||||
<div className="tanstak-table-filtres">
|
||||
<div className="table-filtres-wrapper">
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface User {
|
||||
fullName: string;
|
||||
company: string | null;
|
||||
email: string;
|
||||
active: boolean;
|
||||
phone: string;
|
||||
genderType: 'MALE' | 'FEMALE';
|
||||
birthday: string | null;
|
||||
createdAt: string;
|
||||
subscriptionType: 'DEMO' | string;
|
||||
tariffs: null;
|
||||
tariffInfo: TariffInfo;
|
||||
permission: null;
|
||||
verifiedStatus: 'VERIFIED' | 'PENDING' | 'UNVERIFIED';
|
||||
}
|
||||
|
||||
interface TariffInfo {
|
||||
id: string;
|
||||
status: 'EXPIRED' | 'ACTIVE' | 'PENDING';
|
||||
startTariff: string;
|
||||
endTariff: string;
|
||||
tariffId: number;
|
||||
tariffName: string;
|
||||
tokens: number;
|
||||
maxFileOnDisk: number;
|
||||
currentFileOnDisk: number;
|
||||
currentFileCounts: number;
|
||||
maxFileCounts: number;
|
||||
}
|
||||
|
||||
export function UserInfoModalWindow({
|
||||
userInfo,
|
||||
setWindowClose,
|
||||
setWindowChildren
|
||||
}: {
|
||||
userInfo: User, // Исправлено: ожидаем User, а не TariffInfo
|
||||
setWindowClose: any,
|
||||
setWindowChildren: any
|
||||
}) {
|
||||
const t = useTranslations('Global');
|
||||
|
||||
const formatBytes = (bytes: number) => {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const getStatusBadge = (status: string) => {
|
||||
const statusConfig = {
|
||||
'ACTIVE': { class: 'status-active', text: 'Active' },
|
||||
'EXPIRED': { class: 'status-expired', text: 'Expired' },
|
||||
'PENDING': { class: 'status-pending', text: 'Pending' },
|
||||
'VERIFIED': { class: 'status-verified', text: 'Verified' },
|
||||
'UNVERIFIED': { class: 'status-unverified', text: 'Unverified' }
|
||||
};
|
||||
const config = statusConfig[status as keyof typeof statusConfig];
|
||||
return config ? <span className={`status-badge ${config.class}`}>{config.text}</span> : status;
|
||||
};
|
||||
|
||||
const getGenderText = (gender: string) => {
|
||||
return gender === 'MALE' ? 'Male' : 'Female';
|
||||
};
|
||||
|
||||
console.log(userInfo)
|
||||
|
||||
return (
|
||||
<div className="modal-window-view-user">
|
||||
<div className="modal-window-view-user-content">
|
||||
<div className="user-info-wrapper">
|
||||
{/* Карточка информации о пользователе */}
|
||||
<div className="user-info-card">
|
||||
<div className="info-header">
|
||||
<h4>{t('userInformation') || 'User Information'}</h4>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('fullName') || 'Full Name'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.fullName}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('email')}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.email}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('phone') || 'Phone'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.phone}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('company')}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.company || '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('gender') || 'Gender'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{getGenderText(userInfo.genderType)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('birthday') || 'Birthday'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.birthday ? formatDate(userInfo.birthday) : '-'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('createdAt') || 'Registered'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{formatDateTime(userInfo.createdAt)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('verifiedStatus') || 'Verification Status'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{getStatusBadge(userInfo.verifiedStatus)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('active')}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.active ? 'active' : 'not active'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Карточка информации о тарифе */}
|
||||
<div className="user-info-card">
|
||||
<div className="info-header">
|
||||
<h4>{t('tariffInformation') || 'Tariff Information'}</h4>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('tariffName') || 'Tariff'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.tariffInfo.tariffName}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('tariffStatus') || 'Status'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{getStatusBadge(userInfo.tariffInfo.status)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('tokens') || 'Tokens'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.tariffInfo.tokens || 0}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('startDate') || 'Start Date'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{formatDate(userInfo.tariffInfo.startTariff)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('endDate') || 'End Date'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{formatDate(userInfo.tariffInfo.endTariff)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Карточка использования диска */}
|
||||
<div className="user-info-card">
|
||||
<div className="info-header">
|
||||
<h4>{t('storageInformation') || 'Storage Information'}</h4>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('diskUsage') || 'Disk Usage'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{formatBytes(userInfo.tariffInfo.currentFileOnDisk)} / {formatBytes(userInfo.tariffInfo.maxFileOnDisk)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="info-item">
|
||||
<div className="info-label">
|
||||
{t('fileCount') || 'Files'}:
|
||||
</div>
|
||||
<div className="info-value">
|
||||
{userInfo.tariffInfo.currentFileCounts} / {userInfo.tariffInfo.maxFileCounts}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -173,7 +173,22 @@
|
||||
"my-content-description": "Managing protected files and copyright control",
|
||||
"monitoring-copyright-infringements": "Monitoring copyright infringements",
|
||||
"monitoring-copyright-infringements-description": "Monitor and manage copyright infringement of your protected content online",
|
||||
"referral-program-description": "Invite your friends and earn up to 25% commission on every purchase! Share the link and get rewarded."
|
||||
"referral-program-description": "Invite your friends and earn up to 25% commission on every purchase! Share the link and get rewarded.",
|
||||
"userInformation": "User Information",
|
||||
"tariffInformation": "Tariff Information",
|
||||
"fullName": "Full Name",
|
||||
"createdAt": "Registration Date",
|
||||
"verifiedStatus": "Verification Status",
|
||||
"active": "Status",
|
||||
"subscriptionType": "Subscription Type",
|
||||
"tariffName": "Tariff Plan",
|
||||
"tariffStatus": "Tariff Status",
|
||||
"startDate": "Start Date",
|
||||
"endDate": "End Date",
|
||||
"storageInformation": "Storage Information",
|
||||
"diskUsage": "Disk Usage",
|
||||
"diskUsagePercent": "Usage",
|
||||
"fileCount": "Files"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"and": "and",
|
||||
|
||||
@@ -173,7 +173,22 @@
|
||||
"my-content-description": "Управление защищенными файлами и контролем авторских прав",
|
||||
"monitoring-copyright-infringements": "Мониторинг нарушений авторских прав",
|
||||
"monitoring-copyright-infringements-description": "Отслеживание и управление нарушениями авторских прав на ваш защищенный контент в интернете",
|
||||
"referral-program-description": "Приглашайте друзей и зарабатывайте до 25% комиссии с каждой их покупки! Делитесь ссылкой, получайте вознаграждение."
|
||||
"referral-program-description": "Приглашайте друзей и зарабатывайте до 25% комиссии с каждой их покупки! Делитесь ссылкой, получайте вознаграждение.",
|
||||
"userInformation": "Информация о пользователе",
|
||||
"tariffInformation": "Информация о тарифе",
|
||||
"fullName": "ФИО",
|
||||
"createdAt": "Дата регистрации",
|
||||
"verifiedStatus": "Статус верификации",
|
||||
"active": "Статус",
|
||||
"subscriptionType": "Тип подписки",
|
||||
"tariffName": "Тарифный план",
|
||||
"tariffStatus": "Статус тарифа",
|
||||
"startDate": "Дата начала",
|
||||
"endDate": "Дата окончания",
|
||||
"storageInformation": "Информация о хранилище",
|
||||
"diskUsage": "Использование диска",
|
||||
"diskUsagePercent": "Заполнение",
|
||||
"fileCount": "Файлы"
|
||||
},
|
||||
"Login-register-form": {
|
||||
"and": "и",
|
||||
|
||||
Reference in New Issue
Block a user