add permisson for create admin block

This commit is contained in:
smanylov
2026-05-20 16:23:19 +07:00
parent 5117878185
commit 85281f1fb1
13 changed files with 405 additions and 201 deletions
@@ -1,14 +1,13 @@
'use client' 'use client'
import TanstakStuffTable from '@/app/ui/staff-management/tanstak-stuff-table'; import TanstakStuffTable from '@/app/ui/staff-management/tanstak-stuff-table';
import StaffManagement from '@/app/ui/staff-management/staff-add-management'; import { CreateStaffForm } from '@/app/ui/forms/create-staff-form';
export default function Page() { export default function Page() {
return ( return (
<div> <div>
<StaffManagement /> <CreateStaffForm />
<TanstakStuffTable /> <TanstakStuffTable />
{/* <CreateStaffForm /> */}
</div> </div>
) )
} }
+20 -18
View File
@@ -45,7 +45,23 @@ export async function fetchStuffList(page?: number, size?: number, sortBy?: stri
} }
} }
export async function createStuff(fullName: string, email: string, password: string) { 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 createStuff(fullName: string, email: string, password: string, permissions: Permissions, changePassword: boolean) {
const token = await getSessionData('token'); const token = await getSessionData('token');
try { try {
@@ -60,7 +76,9 @@ export async function createStuff(fullName: string, email: string, password: str
full_name: fullName, full_name: fullName,
email: email, email: email,
password: password, password: password,
is_super_admin: false is_super_admin: false,
change_password: changePassword,
permissions: permissions,
} }
}), }),
headers: { headers: {
@@ -130,22 +148,6 @@ export async function removeStuff(id: number) {
} }
} }
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) { export async function updateStuffPermissions(id: number, permissions: Permissions) {
const token = await getSessionData('token'); const token = await getSessionData('token');
+1 -1
View File
@@ -2,7 +2,7 @@
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import AdminNavLinks from '@/app/ui/navigation/admin-nav-links'; import AdminNavLinks from '@/app/ui/navigation/admin-nav-links';
import AdminHeaderPanel from '@/app/ui/admin-header-panel'; import AdminHeaderPanel from '@/app/ui/header/admin-header-panel';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
export default function AdminLayoutWrapper({ export default function AdminLayoutWrapper({
+5
View File
@@ -1,9 +1,14 @@
import { QueryClient } from '@tanstack/react-query'; import { QueryClient } from '@tanstack/react-query';
import { fetchTokensGeneralStatistic, fetchProtectedContentGeneralStatistic, fetchTariffStatistic, fetchUserFilesTopStatistic, fetchUserGeneralStatistic, fetchViolationGeneralStatistic } from '@/app/actions/statisticActions'; import { fetchTokensGeneralStatistic, fetchProtectedContentGeneralStatistic, fetchTariffStatistic, fetchUserFilesTopStatistic, fetchUserGeneralStatistic, fetchViolationGeneralStatistic } from '@/app/actions/statisticActions';
import { useSubscribeGeneralStatistic } from '@/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic'; import { useSubscribeGeneralStatistic } from '@/app/hooks/react-query/dashboard/useSubscribeGeneralStatistic';
import { fetchEmployeInfo } from '@/app/actions/stuffActions';
export async function prefetchLayoutQueries(queryClient: QueryClient) { export async function prefetchLayoutQueries(queryClient: QueryClient) {
await Promise.all([ await Promise.all([
queryClient.prefetchQuery({
queryKey: ['fetchEmployeInfo'],
queryFn: () => fetchEmployeInfo()
}),
queryClient.prefetchQuery({ queryClient.prefetchQuery({
queryKey: ['incomeGeneralStatistic'], queryKey: ['incomeGeneralStatistic'],
queryFn: () => fetchTokensGeneralStatistic() queryFn: () => fetchTokensGeneralStatistic()
+28 -7
View File
@@ -6,6 +6,19 @@
overflow-y: auto; overflow-y: auto;
background: white; background: white;
border-radius: 12px; border-radius: 12px;
}
.create-staff-window {
background: var(--surface);
box-shadow: var(--shadow);
border-radius: 12px;
overflow: hidden;
margin-bottom: 24px;
padding: 1.5rem;
}
.edit-permissions-modal,
.create-staff-window {
.modal-header { .modal-header {
margin-bottom: 24px; margin-bottom: 24px;
@@ -20,6 +33,21 @@
} }
} }
.permissions-section-title {
border-top: 1px solid #e9ecef;
margin-bottom: 20px;
padding-top: 20px;
}
.checkbox-label {
display: flex;
gap: 10px;
input {
width: auto;
}
}
.permissions-list { .permissions-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -27,17 +55,10 @@
margin-bottom: 28px; margin-bottom: 28px;
.permission-item { .permission-item {
/* border: 1px solid #e9ecef; */
border-radius: 12px; border-radius: 12px;
transition: all 0.2s ease; transition: all 0.2s ease;
/* &:hover {
border-color: #dee2e6;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
} */
.permission-header { .permission-header {
/* padding: 20px; */
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
+5
View File
@@ -289,4 +289,9 @@
.password-wrapper { .password-wrapper {
position: relative; position: relative;
}
.checkbox-label {
display: flex;
gap: 10px;
} }
+275 -119
View File
@@ -1,13 +1,12 @@
'use client' 'use client'
import styles from '@/app/styles/module/login.module.scss' import styles from '@/app/styles/module/login.module.scss'
import { useActionState, useState, useRef, useEffect } from 'react'; import { useActionState, useState, useEffect } from 'react';
import { createStuff } from '@/app/actions/stuffActions'; import { createStuff } from '@/app/actions/stuffActions';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { IconEye } from '@/app/ui/icons/icons'; import { IconEye } from '@/app/ui/icons/icons';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { z } from 'zod'; import { z } from 'zod';
const createStuffSchema = z.object({ const createStuffSchema = z.object({
@@ -16,23 +15,93 @@ const createStuffSchema = z.object({
.max(100, 'full-name-too-long') .max(100, 'full-name-too-long')
.trim(), .trim(),
email: z.string() email: z.string()
.email('invalid-email') .email('register-error-email-invalid')
.toLowerCase() .toLowerCase()
.trim(), .trim(),
password: z.string() password: z.string()
.min(6, 'password-too-short') .min(4, 'register-error-password-min')
.max(50, 'password-too-long'), .max(124, 'register-error-password-max'),
changePassword: z.boolean().default(true),
permissions: z.object({
subscriptions: z.number().min(0).max(3),
mailings: z.number().min(0).max(3),
complaints: z.number().min(0).max(3),
agreements: z.number().min(0).max(3),
staff: z.number().min(0).max(3),
content_moderation: z.number().min(0).max(3),
users: z.number().min(0).max(3),
money: z.number().min(0).max(3),
kyc: z.number().min(0).max(3),
claims: z.number().min(0).max(3),
api: z.number().min(0).max(3),
dash: z.number().min(0).max(3),
tariffs: z.number().min(0).max(3),
}).default({
subscriptions: 0,
mailings: 0,
complaints: 0,
agreements: 0,
staff: 0,
content_moderation: 0,
users: 0,
money: 0,
kyc: 0,
claims: 0,
api: 0,
dash: 0,
tariffs: 0,
})
}) })
export default function CreateStaffForm({ onSuccess }: { onSuccess?: () => void }) { export function CreateStaffForm({ onSuccess }: { onSuccess?: () => void }) {
const [state, formAction, isPending] = useActionState( const [state, formAction, isPending] = useActionState(
createStuffAction, createStuffAction,
undefined, undefined,
); );
const t = useTranslations('Login-register-form'); const t = useTranslations('Login-register-form');
const tStaff = useTranslations('Staff'); const tGeneral = useTranslations('Global');
const tPermissions = useTranslations('Permissions');
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [changePassword, setChangePassword] = useState(true);
const [permissions, setPermissions] = useState({
subscriptions: 0,
mailings: 0,
complaints: 0,
agreements: 0,
staff: 0,
content_moderation: 0,
users: 0,
money: 0,
kyc: 0,
claims: 0,
api: 0,
dash: 0,
tariffs: 0,
});
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 typeof permissions, value: number) => {
setPermissions(prev => ({
...prev,
[key]: value
}));
};
function toggleShowPassword() { function toggleShowPassword() {
setShowPassword(!showPassword); setShowPassword(!showPassword);
@@ -42,7 +111,7 @@ export default function CreateStaffForm({ onSuccess }: { onSuccess?: () => void
useEffect(() => { useEffect(() => {
if (state?.success) { if (state?.success) {
toast.success(tStaff('staff-created-successfully')); toast.success(tGeneral('staff-created-successfully'));
queryClient.invalidateQueries({ queryKey: ['stuffList'] }); queryClient.invalidateQueries({ queryKey: ['stuffList'] });
if (onSuccess) { if (onSuccess) {
onSuccess(); onSuccess();
@@ -51,109 +120,182 @@ export default function CreateStaffForm({ onSuccess }: { onSuccess?: () => void
if (state?.error?.server) { if (state?.error?.server) {
toast.error(t(state.error.server)); toast.error(t(state.error.server));
} }
}, [state, queryClient, onSuccess, t, tStaff]); }, [state, queryClient, onSuccess, t, tGeneral]);
return ( return (
<form <div
className={`${styles['form-wrapper']}`} className="create-staff-window"
action={formAction}
autoComplete="off"
> >
{/* Поле Email */} <form
<div className={`${styles['form-group']}`}> className={`${styles['']}`}
<label className={`${styles['form-label']}`}> action={formAction}
{t('email-adress')} autoComplete="off"
</label>
<input
type="email"
id="email"
name="email"
className={`${styles['form-input']}`}
placeholder={t('enter-email')}
defaultValue={state?.previousState?.email || ''}
autoComplete="off"
onChange={(e) => {
e.target.value = e.target.value.toLowerCase();
}}
/>
{state?.error?.email && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.email)}
</p>
)}
</div>
{/* Поле Password */}
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{t('password')}
</label>
<div className={`${styles['password-wrapper']}`}>
<input
type={showPassword ? "text" : "password"}
id="password"
name="password"
className={`${styles['form-input']} ${styles['password']}`}
placeholder={t('enter-password')}
autoComplete="new-password"
/>
<button
onClick={toggleShowPassword}
type="button"
className={`show-password-button ${showPassword ? 'show' : ''}`}
>
<IconEye />
</button>
</div>
{state?.error?.password && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.password)}
</p>
)}
</div>
{/* Поле Full Name */}
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{tStaff('full-name')}
</label>
<input
type="text"
id="fullName"
name="fullName"
className={`${styles['form-input']}`}
placeholder={tStaff('enter-full-name')}
defaultValue={state?.previousState?.fullName || ''}
autoComplete="off"
/>
{state?.error?.fullName && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.fullName)}
</p>
)}
</div>
{/* Общая ошибка сервера */}
{state?.error?.server && !state.success && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.server)}
</p>
)}
{/* Кнопка отправки */}
<button
type="submit"
className={`${styles['btn']}`}
disabled={isPending}
> >
{tStaff('create-staff')} {/* Поле Email */}
{isPending && ( <div className={`${styles['form-group']}`}>
<div className="loading-animation"> <label className={`${styles['form-label']}`}>
<div className="global-spinner"></div> {t('email-adress')}
</label>
<input
type="email"
id="email"
name="email"
className={`${styles['form-input']}`}
placeholder={t('enter-email')}
defaultValue={state?.previousState?.email || ''}
autoComplete="off"
onChange={(e) => {
e.target.value = e.target.value.toLowerCase();
}}
/>
{state?.error?.email && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.email)}
</p>
)}
</div>
{/* Поле Password */}
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{t('password')}
</label>
<div className={`${styles['password-wrapper']}`}>
<input
type={showPassword ? "text" : "password"}
id="password"
name="password"
className={`${styles['form-input']} ${styles['password']}`}
placeholder={t('enter-password')}
autoComplete="new-password"
/>
<button
onClick={toggleShowPassword}
type="button"
className={`show-password-button ${showPassword ? 'show' : ''}`}
>
<IconEye />
</button>
</div> </div>
{state?.error?.password && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.password)}
</p>
)}
</div>
{/* Поле Full Name */}
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']}`}>
{tGeneral('full-name')}
</label>
<input
type="text"
id="fullName"
name="fullName"
className={`${styles['form-input']}`}
placeholder={tGeneral('enter-full-name')}
defaultValue={state?.previousState?.fullName || ''}
autoComplete="off"
/>
{state?.error?.fullName && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.fullName)}
</p>
)}
</div>
{/* Чекбокс */}
<div className={`${styles['form-group']}`}>
<label className={`${styles['form-label']} ${styles['checkbox-label']}`}>
<span>{tGeneral('require-password-change')}</span>
<input
type="checkbox"
name="changePassword"
checked={changePassword}
onChange={(e) => setChangePassword(e.target.checked)}
disabled={isPending}
/>
</label>
</div>
<div className="permissions-section">
<h3 className="permissions-section-title">
{tGeneral('permissions')}
</h3>
<div className="permissions-list">
{permissionsConfig.map((perm) => {
const currentValue = permissions[perm.key as keyof typeof permissions];
return (
<div key={perm.key} className="permission-item">
<div className="permission-header">
<div className="permission-info">
<h4 className="permission-title">{perm.label}</h4>
<p className="permission-description">{perm.description}</p>
</div>
<div className="permission-controls">
<div className="permission-level-buttons">
<input type="hidden" name={`permissions.${perm.key}`} value={currentValue} />
<button
type="button"
onClick={() => handlePermissionChange(perm.key as keyof typeof permissions, 0)}
className={`permission-level-btn ${currentValue === 0 ? 'active no-access' : ''}`}
title={tPermissions('no-access')}
disabled={isPending}
>
<span className="btn-text">{tPermissions('no-access')}</span>
</button>
<button
type="button"
onClick={() => handlePermissionChange(perm.key as keyof typeof permissions, 1)}
className={`permission-level-btn ${currentValue === 1 ? 'active read-only' : ''}`}
title={tPermissions('read-only')}
disabled={isPending}
>
<span className="btn-text">{tPermissions('read-only')}</span>
</button>
<button
type="button"
onClick={() => handlePermissionChange(perm.key as keyof typeof permissions, 3)}
className={`permission-level-btn ${currentValue === 3 ? 'active full-access' : ''}`}
title={tPermissions('full-access')}
disabled={isPending}
>
<span className="btn-text">{tPermissions('full-access')}</span>
</button>
</div>
</div>
</div>
</div>
);
})}
</div>
</div>
{state?.error?.server && !state.success && (
<p className={`${styles['form-error']}`}>
{t(state?.error?.server)}
</p>
)} )}
</button>
</form> <button
type="submit"
className={`${styles['btn']}`}
disabled={isPending}
>
{tGeneral('create')}
{isPending && (
<div className="loading-animation">
<div className="global-spinner"></div>
</div>
)}
</button>
</form>
</div>
) )
} }
@@ -172,11 +314,30 @@ async function createStuffAction(
const fullName = formData.get('fullName') as string || ''; const fullName = formData.get('fullName') as string || '';
const email = formData.get('email') as string || ''; const email = formData.get('email') as string || '';
const password = formData.get('password') as string || ''; const password = formData.get('password') as string || '';
const changePassword = formData.get('changePassword') === 'on';
const permissions = {
subscriptions: parseInt(formData.get('permissions.subscriptions') as string) || 0,
mailings: parseInt(formData.get('permissions.mailings') as string) || 0,
complaints: parseInt(formData.get('permissions.complaints') as string) || 0,
agreements: parseInt(formData.get('permissions.agreements') as string) || 0,
staff: parseInt(formData.get('permissions.staff') as string) || 0,
content_moderation: parseInt(formData.get('permissions.content_moderation') as string) || 0,
users: parseInt(formData.get('permissions.users') as string) || 0,
money: parseInt(formData.get('permissions.money') as string) || 0,
kyc: parseInt(formData.get('permissions.kyc') as string) || 0,
claims: parseInt(formData.get('permissions.claims') as string) || 0,
api: parseInt(formData.get('permissions.api') as string) || 0,
dash: parseInt(formData.get('permissions.dash') as string) || 0,
tariffs: parseInt(formData.get('permissions.tariffs') as string) || 0,
};
const validatedFields = createStuffSchema.safeParse({ const validatedFields = createStuffSchema.safeParse({
fullName, fullName,
email, email,
password password,
changePassword,
permissions
}); });
if (!validatedFields.success) { if (!validatedFields.success) {
@@ -205,10 +366,9 @@ async function createStuffAction(
} }
try { try {
const { fullName, email, password } = validatedFields.data; const { fullName, email, password, changePassword, permissions } = validatedFields.data;
// Вызов API для создания сотрудника const result = await createStuff(fullName, email, password, permissions, changePassword);
const result = await createStuff(fullName, email, password);
if (result) { if (result) {
return { return {
@@ -227,16 +387,12 @@ async function createStuffAction(
} catch (error) { } catch (error) {
const errors: Record<string, string> = {} const errors: Record<string, string> = {}
switch (error) { if (error === 'email-already-exists') {
case 'email-already-exists': errors['email'] = 'email-already-exists';
errors['email'] = 'email-already-exists'; } else if (error === 'invalid-email') {
break; errors['email'] = 'invalid-email';
case 'invalid-email': } else {
errors['email'] = 'invalid-email'; errors['server'] = 'request-ended-with-an-error';
break;
default:
errors['server'] = 'request-ended-with-an-error';
break;
} }
return { return {
@@ -1,6 +1,7 @@
import Link from 'next/link'; import Link from 'next/link';
import LanguageSwitcher from '@/app/components/LanguageSwitcher'; import LanguageSwitcher from '@/app/components/LanguageSwitcher';
import packageJson from '../../../package.json'; import packageJson from '../../../../package.json';
import { HeaderUserInfoBadge } from '@/app/ui/header/header-user-info';
export default function AdminHeaderPanel() { export default function AdminHeaderPanel() {
return ( return (
@@ -15,15 +16,7 @@ export default function AdminHeaderPanel() {
</div> </div>
<div className="admin-header-right"> <div className="admin-header-right">
{/* <LanguageSwitcher /> */} {/* <LanguageSwitcher /> */}
<div className="admin-user-info"> <HeaderUserInfoBadge />
<div className="admin-user-avatar">
+
</div>
<div className="admin-user-details">
<span className="name">name name name</span>
<span className="role">role</span>
</div>
</div>
<Link <Link
href={'/pages/dashboard'} href={'/pages/dashboard'}
className="btn btn-primary" className="btn btn-primary"
+19
View File
@@ -0,0 +1,19 @@
'use client'
import {useEmployeInfo} from '@/app/hooks/react-query/useEmployeInfo';
export function HeaderUserInfoBadge() {
const { data } = useEmployeInfo();
return (
<div className="admin-user-info">
{/* <div className="admin-user-avatar">
+
</div> */}
<div className="admin-user-details">
<span className="name">{data?.full_name ? data?.full_name : '---'}</span>
<span className="role">{data?.is_super_admin ? 'super-admin' : 'stuff'}</span>
</div>
</div>
)
}
@@ -1,6 +1,8 @@
/* for remove */
import { useState } from 'react'; import { useState } from 'react';
import ModalWindow from '@/app/components/modalWindow'; import ModalWindow from '@/app/components/modalWindow';
import CreateStaffForm from '@/app/ui/forms/create-staff-form'; import { CreateStaffForm } from '@/app/ui/forms/create-staff-form';
export default function StaffManagement() { export default function StaffManagement() {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@@ -9,7 +11,8 @@ export default function StaffManagement() {
<div <div
className="admin-content mb-4" className="admin-content mb-4"
> >
<div <CreateStaffForm />
{/* <div
className="p-4" className="p-4"
> >
<button <button
@@ -22,10 +25,10 @@ export default function StaffManagement() {
<ModalWindow state={isModalOpen} callBack={setIsModalOpen}> <ModalWindow state={isModalOpen} callBack={setIsModalOpen}>
<div className="p-6 min-w-[400px]"> <div className="p-6 min-w-[400px]">
<h2 className="text-2xl font-bold mb-4">Создание сотрудника</h2> <h2 className="text-2xl font-bold mb-4">Создание сотрудника</h2>
<CreateStaffForm onSuccess={() => setIsModalOpen(false)} /> <CreateStaffForm />
</div> </div>
</ModalWindow> </ModalWindow>
</div> </div> */}
</div> </div>
); );
} }
@@ -63,7 +63,6 @@ export default function TanstakStuffTable() {
const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null); const [openWindowChildren, setOpenWindowChildren] = useState<ReactNode>(null);
const [selectedStaff, setSelectedStaff] = useState<StaffMember | null>(null); const [selectedStaff, setSelectedStaff] = useState<StaffMember | null>(null);
const [confirmRemove, setConfirmRemove] = useState<{ id: number; name: string } | null>(null); const [confirmRemove, setConfirmRemove] = useState<{ id: number; name: string } | null>(null);
const tStaff = useTranslations('Staff');
const debouncedSetSearchQuery = useDebouncedCallback( const debouncedSetSearchQuery = useDebouncedCallback(
(value: string) => { (value: string) => {
@@ -117,12 +116,12 @@ export default function TanstakStuffTable() {
const removeMutation = useMutation({ const removeMutation = useMutation({
mutationFn: (id: number) => removeStuff(id), mutationFn: (id: number) => removeStuff(id),
onSuccess: () => { onSuccess: () => {
toast.success(tStaff('staff-removed-successfully')); toast.success(t('staff-removed-successfully'));
queryClient.invalidateQueries({ queryKey: ['stuffList'] }); queryClient.invalidateQueries({ queryKey: ['stuffList'] });
setConfirmRemove(null); setConfirmRemove(null);
}, },
onError: (error: Error) => { onError: (error: Error) => {
toast.error(tStaff(error.message || 'removal-failed')); toast.error(t(error.message || 'removal-failed'));
setConfirmRemove(null); setConfirmRemove(null);
} }
}); });
@@ -131,13 +130,13 @@ export default function TanstakStuffTable() {
mutationFn: ({ id, permissions }: { id: number; permissions: StaffMember['permissions'] }) => mutationFn: ({ id, permissions }: { id: number; permissions: StaffMember['permissions'] }) =>
updateStuffPermissions(id, permissions), updateStuffPermissions(id, permissions),
onSuccess: () => { onSuccess: () => {
toast.success(tStaff('permissions-updated-successfully')); toast.success(t('permissions-updated-successfully'));
queryClient.invalidateQueries({ queryKey: ['stuffList'] }); queryClient.invalidateQueries({ queryKey: ['stuffList'] });
queryClient.invalidateQueries({ queryKey: ['employeInfo'] }); queryClient.invalidateQueries({ queryKey: ['employeInfo'] });
setOpenWindow(false); setOpenWindow(false);
}, },
onError: (error: Error) => { onError: (error: Error) => {
toast.error(tStaff(error.message || 'permissions-update-failed')); toast.error(t(error.message || 'permissions-update-failed'));
} }
}); });
+18 -17
View File
@@ -190,7 +190,21 @@
"diskUsagePercent": "Usage", "diskUsagePercent": "Usage",
"fileCount": "Files", "fileCount": "Files",
"employee": "Employee", "employee": "Employee",
"role": "Role" "role": "Role",
"enter-full-name": "Enter full name",
"staff-created-successfully": "Staff member created successfully",
"passwords-do-not-match": "Passwords do not match",
"email-already-exists": "User with this email already exists",
"staff-removed-successfully": "The employee has been successfully removed",
"removal-failed": "Error deleting employee",
"permissions-updated-successfully": "Permissions updated successfully",
"permissions-update-failed": "Failed to update permissions",
"staff-creation": "Staff creation",
"password": "Password",
"require-password-change": "Require password change on first login",
"permissions": "Permissions",
"creating": "Creation...",
"create": "Create"
}, },
"Login-register-form": { "Login-register-form": {
"and": "and", "and": "and",
@@ -227,7 +241,7 @@
"register-error-name-max": "The name length must not exceed 100 characters.", "register-error-name-max": "The name length must not exceed 100 characters.",
"register-error-password-one-digit": "Password must contain at least 1 digit.", "register-error-password-one-digit": "Password must contain at least 1 digit.",
"register-error-password-one-letter": "Password must contain at least 1 letter.", "register-error-password-one-letter": "Password must contain at least 1 letter.",
"register-error-password-min": "Password must be at least 8 characters long.", "register-error-password-min": "Password must be at least 4 characters long.",
"register-error-phone-min": "Please enter a phone number.", "register-error-phone-min": "Please enter a phone number.",
"register-error-company-name-min": "The company name must contain at least 2 characters.", "register-error-company-name-min": "The company name must contain at least 2 characters.",
"register-error-company-name-max": "The company name must exceed 200 characters.", "register-error-company-name-max": "The company name must exceed 200 characters.",
@@ -258,22 +272,9 @@
"register-error-phone-not-allowed-symbols": "", "register-error-phone-not-allowed-symbols": "",
"register-error-password-not-allowed-symbols": "Password contains invalid characters.", "register-error-password-not-allowed-symbols": "Password contains invalid characters.",
"confirmation-code": "Confirmation code", "confirmation-code": "Confirmation code",
"enter-confirmation-code": "Enter the confirmation code" "enter-confirmation-code": "Enter the confirmation code",
},
"Staff": {
"full-name": "Full Name",
"enter-full-name": "Enter full name",
"confirm-password": "Confirm Password",
"create-staff": "Create Staff",
"staff-created-successfully": "Staff member created successfully",
"full-name-too-short": "Full name must be at least 2 characters", "full-name-too-short": "Full name must be at least 2 characters",
"full-name-too-long": "Full name must not exceed 100 characters", "full-name-too-long": "Full name must not exceed 100 characters"
"passwords-do-not-match": "Passwords do not match",
"email-already-exists": "User with this email already exists",
"staff-removed-successfully": "The employee has been successfully removed",
"removal-failed": "Error deleting employee",
"permissions-updated-successfully": "Permissions updated successfully",
"permissions-update-failed": "Failed to update permissions"
}, },
"Permissions": { "Permissions": {
"edit-permissions": "Edit Permissions", "edit-permissions": "Edit Permissions",
+18 -17
View File
@@ -190,7 +190,21 @@
"diskUsagePercent": "Заполнение", "diskUsagePercent": "Заполнение",
"fileCount": "Файлы", "fileCount": "Файлы",
"employee": "Сотрудник", "employee": "Сотрудник",
"role": "Роль" "role": "Роль",
"enter-full-name": "Введите полное имя",
"staff-created-successfully": "Сотрудник успешно создан",
"passwords-do-not-match": "Пароли не совпадают",
"email-already-exists": "Пользователь с таким адресом электронной почты уже существует",
"staff-removed-successfully": "Сотрудник успешно удален",
"removal-failed": "Ошибка при удалении сотрудника",
"permissions-updated-successfully": "Права доступа успешно обновлены",
"permissions-update-failed": "Не удалось обновить права доступа",
"staff-creation": "Создание персонала",
"password": "Пароль",
"require-password-change": "Требовать смену пароля при первом входе",
"permissions": "Доступы",
"creating": "Создание...",
"create": "Создать"
}, },
"Login-register-form": { "Login-register-form": {
"and": "и", "and": "и",
@@ -227,7 +241,7 @@
"register-error-name-max": "Длина имени не должна превышать 100 символов.", "register-error-name-max": "Длина имени не должна превышать 100 символов.",
"register-error-password-one-digit": "Пароль должен содержать как минимум 1 цифру.", "register-error-password-one-digit": "Пароль должен содержать как минимум 1 цифру.",
"register-error-password-one-letter": "Пароль должен содержать как минимум 1 букву.", "register-error-password-one-letter": "Пароль должен содержать как минимум 1 букву.",
"register-error-password-min": "Длина пароля должна быть не менее 8 символов.", "register-error-password-min": "Длина пароля должна быть не менее 4 символов.",
"register-error-phone-min": "Пожалуйста, введите телефонный номер.", "register-error-phone-min": "Пожалуйста, введите телефонный номер.",
"register-error-company-name-min": "Название компании должно содержать не менее 2 символов.", "register-error-company-name-min": "Название компании должно содержать не менее 2 символов.",
"register-error-company-name-max": "Название компании должно превышать 200 символов.", "register-error-company-name-max": "Название компании должно превышать 200 символов.",
@@ -258,22 +272,9 @@
"register-error-phone-not-allowed-symbols": "", "register-error-phone-not-allowed-symbols": "",
"register-error-password-not-allowed-symbols": "Пароль содержит недопустимые символы.", "register-error-password-not-allowed-symbols": "Пароль содержит недопустимые символы.",
"confirmation-code": "Код подтверждения", "confirmation-code": "Код подтверждения",
"enter-confirmation-code": "Введите код подтверждения" "enter-confirmation-code": "Введите код подтверждения",
},
"Staff": {
"full-name": "Полное имя",
"enter-full-name": "Введите полное имя",
"confirm-password": "Подтвердите пароль",
"create-staff": "Создать персонал",
"staff-created-successfully": "Сотрудник успешно создан",
"full-name-too-short": "Полное имя должно состоять как минимум из 2 символов", "full-name-too-short": "Полное имя должно состоять как минимум из 2 символов",
"full-name-too-long": "Полное имя не должно превышать 100 символов", "full-name-too-long": "Полное имя не должно превышать 100 символов"
"passwords-do-not-match": "Пароли не совпадают",
"email-already-exists": "Пользователь с таким адресом электронной почты уже существует",
"staff-removed-successfully": "Сотрудник успешно удален",
"removal-failed": "Ошибка при удалении сотрудника",
"permissions-updated-successfully": "Права доступа успешно обновлены",
"permissions-update-failed": "Не удалось обновить права доступа"
}, },
"Permissions": { "Permissions": {
"edit-permissions": "Редактирование прав доступа", "edit-permissions": "Редактирование прав доступа",