add staff managment permission modal window
This commit is contained in:
@@ -112,7 +112,6 @@ export async function authorization(
|
|||||||
|
|
||||||
export async function logout() {
|
export async function logout() {
|
||||||
const token = await getSessionData('token');
|
const token = await getSessionData('token');
|
||||||
console.log('logout');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
||||||
@@ -133,7 +132,6 @@ export async function logout() {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
let parsed = await response.json();
|
let parsed = await response.json();
|
||||||
console.log(parsed);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -129,4 +129,58 @@ export async function removeStuff(id: number) {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Permissions = {
|
||||||
|
subscriptions: number;
|
||||||
|
mailings: number;
|
||||||
|
complaints: number;
|
||||||
|
agreements: number;
|
||||||
|
staff: number;
|
||||||
|
content_moderation: number;
|
||||||
|
users: number;
|
||||||
|
money: number;
|
||||||
|
kyc: number;
|
||||||
|
claims: number;
|
||||||
|
api: number;
|
||||||
|
dash: number;
|
||||||
|
tariffs: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function updateStuffPermissions(id: number, permissions: Permissions) {
|
||||||
|
const token = await getSessionData('token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/api/admin`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
version: 2,
|
||||||
|
msg_id: 100,
|
||||||
|
token: token,
|
||||||
|
message_body: {
|
||||||
|
action: 'update_permissions',
|
||||||
|
admin_id: id,
|
||||||
|
permissions: permissions
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const parsed = await response.json();
|
||||||
|
|
||||||
|
if (parsed.message_code === 0) {
|
||||||
|
return parsed.message_body;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(`${response.status}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
.edit-permissions-modal {
|
||||||
|
padding: 24px;
|
||||||
|
max-width: 900px;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 85vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 2px solid #f0f0f0;
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1a1a1a;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
|
||||||
|
.permission-item {
|
||||||
|
/* border: 1px solid #e9ecef; */
|
||||||
|
border-radius: 12px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
/* &:hover {
|
||||||
|
border-color: #dee2e6;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
} */
|
||||||
|
|
||||||
|
.permission-header {
|
||||||
|
/* padding: 20px; */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 20px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.permission-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #212529;
|
||||||
|
margin: 0 0 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-description {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6c757d;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.permission-level-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.permission-level-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6c757d;
|
||||||
|
|
||||||
|
.btn-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-text {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: white;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
|
||||||
|
&.no-access {
|
||||||
|
color: #dc3545;
|
||||||
|
background: #fff5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.read-only {
|
||||||
|
color: #0d6efd;
|
||||||
|
background: #f0f7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.full-access {
|
||||||
|
color: #198754;
|
||||||
|
background: #f0fff4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #e9ecef;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-secondary {
|
||||||
|
background: #6c757d;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #5a6268;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn-primary {
|
||||||
|
background: #0d6efd;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover:not(:disabled) {
|
||||||
|
background: #0b5ed7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 50%;
|
||||||
|
border-top-color: white;
|
||||||
|
animation: spin 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scrollbar styling
|
||||||
|
.edit-permissions-modal::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-permissions-modal::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-permissions-modal::-webkit-scrollbar-thumb {
|
||||||
|
background: #888;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
@use './variable.scss' as v;
|
@use './variable.scss' as v;
|
||||||
|
@use './edit-permissions-modal.scss';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #2563eb;
|
--primary-color: #2563eb;
|
||||||
|
|||||||
@@ -227,7 +227,6 @@ async function createStuffAction(
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errors: Record<string, string> = {}
|
const errors: Record<string, string> = {}
|
||||||
|
|
||||||
// Обработка различных ошибок от сервера
|
|
||||||
switch (error) {
|
switch (error) {
|
||||||
case 'email-already-exists':
|
case 'email-already-exists':
|
||||||
errors['email'] = 'email-already-exists';
|
errors['email'] = 'email-already-exists';
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { StaffMember } from './tanstak-stuff-table';
|
||||||
|
|
||||||
|
export function EditPermissionsModal({
|
||||||
|
staff,
|
||||||
|
setWindowClose,
|
||||||
|
onSave,
|
||||||
|
isLoading
|
||||||
|
}: {
|
||||||
|
staff: StaffMember;
|
||||||
|
setWindowClose: (state: boolean) => void;
|
||||||
|
onSave: (permissions: StaffMember['permissions']) => Promise<void>;
|
||||||
|
isLoading: boolean;
|
||||||
|
}) {
|
||||||
|
const t = useTranslations("Global");
|
||||||
|
const tPermissions = useTranslations("Permissions");
|
||||||
|
|
||||||
|
const [permissions, setPermissions] = useState<StaffMember['permissions']>(staff.permissions);
|
||||||
|
|
||||||
|
const permissionsConfig = [
|
||||||
|
{ key: 'subscriptions', label: tPermissions('subscriptions'), description: tPermissions('subscriptions-desc') },
|
||||||
|
{ key: 'mailings', label: tPermissions('mailings'), description: tPermissions('mailings-desc') },
|
||||||
|
{ key: 'complaints', label: tPermissions('complaints'), description: tPermissions('complaints-desc') },
|
||||||
|
{ key: 'agreements', label: tPermissions('agreements'), description: tPermissions('agreements-desc') },
|
||||||
|
{ key: 'staff', label: tPermissions('staff'), description: tPermissions('staff-desc') },
|
||||||
|
{ key: 'content_moderation', label: tPermissions('content_moderation'), description: tPermissions('content_moderation-desc') },
|
||||||
|
{ key: 'users', label: tPermissions('users'), description: tPermissions('users-desc') },
|
||||||
|
{ key: 'kyc', label: tPermissions('kyc'), description: tPermissions('kyc-desc') },
|
||||||
|
{ key: 'money', label: tPermissions('money'), description: tPermissions('money-desc') },
|
||||||
|
{ key: 'claims', label: tPermissions('claims'), description: tPermissions('claims-desc') },
|
||||||
|
{ key: 'api', label: tPermissions('api'), description: tPermissions('api-desc') },
|
||||||
|
{ key: 'dash', label: tPermissions('dash'), description: tPermissions('dash-desc') },
|
||||||
|
{ key: 'tariffs', label: tPermissions('tariffs'), description: tPermissions('tariffs-desc') },
|
||||||
|
];
|
||||||
|
|
||||||
|
const handlePermissionChange = (key: keyof StaffMember['permissions'], value: number) => {
|
||||||
|
setPermissions(prev => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
await onSave(permissions);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="edit-permissions-modal">
|
||||||
|
<div className="modal-header">
|
||||||
|
<h2 className="modal-title">
|
||||||
|
{tPermissions('edit-permissions')}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="permissions-list">
|
||||||
|
{permissionsConfig.map((perm) => {
|
||||||
|
const currentValue = permissions[perm.key as keyof StaffMember['permissions']];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={perm.key} className="permission-item">
|
||||||
|
<div className="permission-header">
|
||||||
|
<div className="permission-info">
|
||||||
|
<h3 className="permission-title">{perm.label}</h3>
|
||||||
|
{/* <p className="permission-description">{perm.description}</p> */}
|
||||||
|
</div>
|
||||||
|
<div className="permission-controls">
|
||||||
|
<div className="permission-level-buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handlePermissionChange(perm.key as keyof StaffMember['permissions'], 0)}
|
||||||
|
className={`permission-level-btn ${currentValue === 0 ? 'active no-access' : ''}`}
|
||||||
|
title={tPermissions('no-access')}
|
||||||
|
>
|
||||||
|
<span className="btn-text">{tPermissions('no-access')}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handlePermissionChange(perm.key as keyof StaffMember['permissions'], 1)}
|
||||||
|
className={`permission-level-btn ${currentValue === 1 ? 'active read-only' : ''}`}
|
||||||
|
title={tPermissions('read-only')}
|
||||||
|
>
|
||||||
|
<span className="btn-text">{tPermissions('read-only')}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handlePermissionChange(perm.key as keyof StaffMember['permissions'], 3)}
|
||||||
|
className={`permission-level-btn ${currentValue === 3 ? 'active full-access' : ''}`}
|
||||||
|
title={tPermissions('full-access')}
|
||||||
|
>
|
||||||
|
<span className="btn-text">{tPermissions('full-access')}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-footer">
|
||||||
|
<button
|
||||||
|
onClick={() => setWindowClose(false)}
|
||||||
|
className="btn btn-secondary"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{t('cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
className="btn btn-primary"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
|
{isLoading ? (
|
||||||
|
<>
|
||||||
|
<span className="spinner"></span>
|
||||||
|
{t('saving')}
|
||||||
|
</>
|
||||||
|
) : t('save-changes')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { StaffMember } from '@/app/ui/staff-management/tanstak-stuff-table';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
|
||||||
|
export function StaffInfoModal({
|
||||||
|
staff,
|
||||||
|
setWindowClose,
|
||||||
|
setWindowChildren
|
||||||
|
}: {
|
||||||
|
staff: StaffMember;
|
||||||
|
setWindowClose: (state: boolean) => void;
|
||||||
|
setWindowChildren: (children: ReactNode) => void;
|
||||||
|
}) {
|
||||||
|
const t = useTranslations("Global");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-6 max-w-2xl">
|
||||||
|
<h2 className="text-2xl font-bold mb-4">Информация о сотруднике</h2>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<strong>ID:</strong> {staff.id}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>ФИО:</strong> {staff.full_name}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Email:</strong> {staff.email}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Статус:</strong> {staff.is_active ? 'Активен' : 'Неактивен'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Роль:</strong> {staff.is_super_admin ? 'Super Admin' : 'Staff'}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Дата создания:</strong> {formatDateTime(staff.created_at)} {formatDate(staff.created_at)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Активный пароль:</strong> {staff.active_password ? 'Да' : 'Нет'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<strong className="block mb-2">Права доступа:</strong>
|
||||||
|
<div className="grid grid-cols-2 gap-2 bg-gray-50 p-3 rounded">
|
||||||
|
{Object.entries(staff.permissions).map(([key, value]) => (
|
||||||
|
<div key={key} className="flex justify-between">
|
||||||
|
<span className="capitalize">{key}:</span>
|
||||||
|
<span className="font-semibold">
|
||||||
|
{value === 1 ? 'Чтение' : value === 2 ? 'Запись' : value === 3 ? 'Полный доступ' : value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-6 flex justify-end">
|
||||||
|
<button
|
||||||
|
onClick={() => setWindowClose(false)}
|
||||||
|
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
|
||||||
|
>
|
||||||
|
{t('close')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -19,9 +19,12 @@ import ModalWindow from '@/app/components/modalWindow';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
import { useStuffList } from '@/app/hooks/react-query/stuff/useStuffList';
|
import { useStuffList } from '@/app/hooks/react-query/stuff/useStuffList';
|
||||||
import { removeStuff } from '@/app/actions/stuffActions';
|
import { removeStuff, updateStuffPermissions } from '@/app/actions/stuffActions';
|
||||||
|
import { EditPermissionsModal } from './EditPermissionsModal'
|
||||||
|
import { StaffInfoModal } from '@/app/ui/staff-management/StaffInfoModal';
|
||||||
|
import { formatDate, formatDateTime } from '@/app/lib/formatDate';
|
||||||
|
|
||||||
interface StaffMember {
|
export interface StaffMember {
|
||||||
id: number;
|
id: number;
|
||||||
full_name: string;
|
full_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
@@ -46,24 +49,6 @@ interface StaffMember {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Форматирование даты из ISO строки
|
|
||||||
const formatDate = (dateString: string) => {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
const day = date.getDate().toString().padStart(2, '0');
|
|
||||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
||||||
const year = date.getFullYear();
|
|
||||||
|
|
||||||
return `${day}.${month}.${year}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatDateTime = (dateString: string) => {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
const hours = date.getHours().toString().padStart(2, '0');
|
|
||||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
||||||
|
|
||||||
return `${hours}:${minutes}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function TanstakStuffTable() {
|
export default function TanstakStuffTable() {
|
||||||
// Состояния
|
// Состояния
|
||||||
const [sorting, setSorting] = useState<SortingState>([]);
|
const [sorting, setSorting] = useState<SortingState>([]);
|
||||||
@@ -128,10 +113,6 @@ export default function TanstakStuffTable() {
|
|||||||
const totalItems = staffList?.length || 0;
|
const totalItems = staffList?.length || 0;
|
||||||
const totalPages = Math.ceil(totalItems / pagination.pageSize);
|
const totalPages = Math.ceil(totalItems / pagination.pageSize);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(staffList);
|
|
||||||
}, [staffList])
|
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const removeMutation = useMutation({
|
const removeMutation = useMutation({
|
||||||
mutationFn: (id: number) => removeStuff(id),
|
mutationFn: (id: number) => removeStuff(id),
|
||||||
@@ -147,6 +128,19 @@ export default function TanstakStuffTable() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const updatePermissionsMutation = useMutation({
|
||||||
|
mutationFn: ({ id, permissions }: { id: number; permissions: StaffMember['permissions'] }) =>
|
||||||
|
updateStuffPermissions(id, permissions),
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success(tStaff('permissions-updated-successfully'));
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['stuffList'] });
|
||||||
|
setOpenWindow(false);
|
||||||
|
},
|
||||||
|
onError: (error: Error) => {
|
||||||
|
toast.error(tStaff(error.message || 'permissions-update-failed'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Определение колонок для сотрудников
|
// Определение колонок для сотрудников
|
||||||
const columns = useMemo<ColumnDef<StaffMember>[]>(
|
const columns = useMemo<ColumnDef<StaffMember>[]>(
|
||||||
() => [
|
() => [
|
||||||
@@ -308,6 +302,13 @@ export default function TanstakStuffTable() {
|
|||||||
>
|
>
|
||||||
<IconEye />
|
<IconEye />
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => handleEditPermissions(row.original)}
|
||||||
|
className="bg-green-500 hover:bg-green-600"
|
||||||
|
title="Редактировать права доступа"
|
||||||
|
>
|
||||||
|
<IconShieldExclamation />
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemove(row.original.id, row.original.full_name)}
|
onClick={() => handleRemove(row.original.id, row.original.full_name)}
|
||||||
className="bg-red-500 hover:bg-red-600"
|
className="bg-red-500 hover:bg-red-600"
|
||||||
@@ -343,13 +344,28 @@ export default function TanstakStuffTable() {
|
|||||||
setConfirmRemove({ id, name });
|
setConfirmRemove({ id, name });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Фильтрация данных (если нужна клиентская фильтрация)
|
const handleEditPermissions = (staff: StaffMember) => {
|
||||||
|
setSelectedStaff(staff);
|
||||||
|
setOpenWindowChildren(() => {
|
||||||
|
return (
|
||||||
|
<EditPermissionsModal
|
||||||
|
staff={staff}
|
||||||
|
setWindowClose={setOpenWindow}
|
||||||
|
onSave={async (permissions) => {
|
||||||
|
await updatePermissionsMutation.mutateAsync({ id: staff.id, permissions });
|
||||||
|
}}
|
||||||
|
isLoading={updatePermissionsMutation.isPending}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
setOpenWindow(true);
|
||||||
|
};
|
||||||
|
|
||||||
const filteredData = useMemo(() => {
|
const filteredData = useMemo(() => {
|
||||||
if (!staffList) {
|
if (!staffList) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Если серверная фильтрация уже применена, возвращаем как есть
|
|
||||||
return staffList;
|
return staffList;
|
||||||
}, [staffList]);
|
}, [staffList]);
|
||||||
|
|
||||||
@@ -586,70 +602,4 @@ export default function TanstakStuffTable() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Компонент модального окна с информацией о сотруднике
|
|
||||||
function StaffInfoModal({
|
|
||||||
staff,
|
|
||||||
setWindowClose,
|
|
||||||
setWindowChildren
|
|
||||||
}: {
|
|
||||||
staff: StaffMember;
|
|
||||||
setWindowClose: (state: boolean) => void;
|
|
||||||
setWindowChildren: (children: ReactNode) => void;
|
|
||||||
}) {
|
|
||||||
const t = useTranslations("Global");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-6 max-w-2xl">
|
|
||||||
<h2 className="text-2xl font-bold mb-4">Информация о сотруднике</h2>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
<div>
|
|
||||||
<strong>ID:</strong> {staff.id}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>ФИО:</strong> {staff.full_name}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Email:</strong> {staff.email}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Статус:</strong> {staff.is_active ? 'Активен' : 'Неактивен'}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Роль:</strong> {staff.is_super_admin ? 'Super Admin' : 'Staff'}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Дата создания:</strong> {formatDateTime(staff.created_at)} {formatDate(staff.created_at)}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<strong>Активный пароль:</strong> {staff.active_password ? 'Да' : 'Нет'}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<strong className="block mb-2">Права доступа:</strong>
|
|
||||||
<div className="grid grid-cols-2 gap-2 bg-gray-50 p-3 rounded">
|
|
||||||
{Object.entries(staff.permissions).map(([key, value]) => (
|
|
||||||
<div key={key} className="flex justify-between">
|
|
||||||
<span className="capitalize">{key}:</span>
|
|
||||||
<span className="font-semibold">
|
|
||||||
{value === 1 ? 'Чтение' : value === 2 ? 'Запись' : value === 3 ? 'Полный доступ' : value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-6 flex justify-end">
|
|
||||||
<button
|
|
||||||
onClick={() => setWindowClose(false)}
|
|
||||||
className="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
|
|
||||||
>
|
|
||||||
{t('close')}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,9 @@
|
|||||||
"storageInformation": "Storage Information",
|
"storageInformation": "Storage Information",
|
||||||
"diskUsage": "Disk Usage",
|
"diskUsage": "Disk Usage",
|
||||||
"diskUsagePercent": "Usage",
|
"diskUsagePercent": "Usage",
|
||||||
"fileCount": "Files"
|
"fileCount": "Files",
|
||||||
|
"employee": "Employee",
|
||||||
|
"role": "Role"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "and",
|
"and": "and",
|
||||||
@@ -269,6 +271,40 @@
|
|||||||
"passwords-do-not-match": "Passwords do not match",
|
"passwords-do-not-match": "Passwords do not match",
|
||||||
"email-already-exists": "User with this email already exists",
|
"email-already-exists": "User with this email already exists",
|
||||||
"staff-removed-successfully": "The employee has been successfully removed",
|
"staff-removed-successfully": "The employee has been successfully removed",
|
||||||
"removal-failed": "Error deleting employee"
|
"removal-failed": "Error deleting employee",
|
||||||
|
"permissions-updated-successfully": "Permissions updated successfully",
|
||||||
|
"permissions-update-failed": "Failed to update permissions"
|
||||||
|
},
|
||||||
|
"Permissions": {
|
||||||
|
"edit-permissions": "Edit Permissions",
|
||||||
|
"no-access": "No Access",
|
||||||
|
"read-only": "Read Only",
|
||||||
|
"full-access": "Full Access",
|
||||||
|
"subscriptions": "Subscriptions",
|
||||||
|
"subscriptions-desc": "Manage user subscriptions",
|
||||||
|
"mailings": "Mailings",
|
||||||
|
"mailings-desc": "Manage email mailings",
|
||||||
|
"complaints": "Complaints",
|
||||||
|
"complaints-desc": "Process user complaints",
|
||||||
|
"agreements": "Agreements",
|
||||||
|
"agreements-desc": "Manage user agreements",
|
||||||
|
"staff": "Staff",
|
||||||
|
"staff-desc": "Manage staff members",
|
||||||
|
"content_moderation": "Content Moderation",
|
||||||
|
"content_moderation-desc": "Moderate content",
|
||||||
|
"users": "Users",
|
||||||
|
"users-desc": "Manage users",
|
||||||
|
"kyc": "Verification",
|
||||||
|
"kyc-desc": "Manage user verification",
|
||||||
|
"money": "Money",
|
||||||
|
"money-desc": "Manage financial operations",
|
||||||
|
"claims": "Claims",
|
||||||
|
"claims-desc": "Process claims",
|
||||||
|
"api": "API",
|
||||||
|
"api-desc": "Manage API access",
|
||||||
|
"dash": "Dashboard",
|
||||||
|
"dash-desc": "Access to dashboard",
|
||||||
|
"tariffs": "Tariffs",
|
||||||
|
"tariffs-desc": "Manage tariffs"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,9 @@
|
|||||||
"storageInformation": "Информация о хранилище",
|
"storageInformation": "Информация о хранилище",
|
||||||
"diskUsage": "Использование диска",
|
"diskUsage": "Использование диска",
|
||||||
"diskUsagePercent": "Заполнение",
|
"diskUsagePercent": "Заполнение",
|
||||||
"fileCount": "Файлы"
|
"fileCount": "Файлы",
|
||||||
|
"employee": "Сотрудник",
|
||||||
|
"role": "Роль"
|
||||||
},
|
},
|
||||||
"Login-register-form": {
|
"Login-register-form": {
|
||||||
"and": "и",
|
"and": "и",
|
||||||
@@ -269,6 +271,40 @@
|
|||||||
"passwords-do-not-match": "Пароли не совпадают",
|
"passwords-do-not-match": "Пароли не совпадают",
|
||||||
"email-already-exists": "Пользователь с таким адресом электронной почты уже существует",
|
"email-already-exists": "Пользователь с таким адресом электронной почты уже существует",
|
||||||
"staff-removed-successfully": "Сотрудник успешно удален",
|
"staff-removed-successfully": "Сотрудник успешно удален",
|
||||||
"removal-failed": "Ошибка при удалении сотрудника"
|
"removal-failed": "Ошибка при удалении сотрудника",
|
||||||
|
"permissions-updated-successfully": "Права доступа успешно обновлены",
|
||||||
|
"permissions-update-failed": "Не удалось обновить права доступа"
|
||||||
|
},
|
||||||
|
"Permissions": {
|
||||||
|
"edit-permissions": "Редактирование прав доступа",
|
||||||
|
"no-access": "Нет доступа",
|
||||||
|
"read-only": "Только чтение",
|
||||||
|
"full-access": "Полный доступ",
|
||||||
|
"subscriptions": "Подписки",
|
||||||
|
"subscriptions-desc": "Управление подписками пользователей",
|
||||||
|
"mailings": "Рассылки",
|
||||||
|
"mailings-desc": "Управление email рассылками",
|
||||||
|
"complaints": "Жалобы",
|
||||||
|
"complaints-desc": "Обработка жалоб пользователей",
|
||||||
|
"agreements": "Соглашения",
|
||||||
|
"agreements-desc": "Управление пользовательскими соглашениями",
|
||||||
|
"staff": "Сотрудники",
|
||||||
|
"staff-desc": "Управление сотрудниками",
|
||||||
|
"content_moderation": "Модерация контента",
|
||||||
|
"content_moderation-desc": "Модерация контента",
|
||||||
|
"users": "Пользователи",
|
||||||
|
"users-desc": "Управление пользователями",
|
||||||
|
"kyc": "Верификация",
|
||||||
|
"kyc-desc": "Управление верификацией пользователей",
|
||||||
|
"money": "Финансы",
|
||||||
|
"money-desc": "Управление финансовыми операциями",
|
||||||
|
"claims": "Претензии",
|
||||||
|
"claims-desc": "Обработка претензий",
|
||||||
|
"api": "API",
|
||||||
|
"api-desc": "Управление API доступами",
|
||||||
|
"dash": "Панель управления",
|
||||||
|
"dash-desc": "Доступ к панеле управления",
|
||||||
|
"tariffs": "Тарифы",
|
||||||
|
"tariffs-desc": "Управление тарифами"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user