feat: Enhance user forms and user management UI (#437)

This commit is contained in:
samanhappy
2025-11-23 13:50:55 +08:00
committed by GitHub
parent ac0b60ed4b
commit 6de3221974
7 changed files with 270 additions and 89 deletions

View File

@@ -57,28 +57,28 @@ const AddUserForm = ({ onAdd, onCancel }: AddUserFormProps) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target; const { name, value, type, checked } = e.target;
setFormData(prev => ({ setFormData((prev) => ({
...prev, ...prev,
[name]: type === 'checkbox' ? checked : value [name]: type === 'checkbox' ? checked : value,
})); }));
}; };
return ( return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
<div className="bg-white p-8 rounded-lg shadow-xl max-w-md w-full mx-4"> <div className="bg-white p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<h2 className="text-xl font-semibold text-gray-800 mb-4">{t('users.addNew')}</h2> <h2 className="text-xl font-bold text-gray-900 mb-6">{t('users.addNew')}</h2>
{error && ( {error && (
<div className="bg-red-100 border-l-4 border-red-500 text-red-700 p-3 mb-4"> <div className="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md">
<p className="text-sm">{error}</p> <p className="text-sm font-medium">{error}</p>
</div> </div>
)} )}
<div className="space-y-4"> <div className="space-y-5">
<div> <div>
<label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
{t('users.username')} * {t('users.username')} <span className="text-red-500">*</span>
</label> </label>
<input <input
type="text" type="text"
@@ -87,7 +87,7 @@ const AddUserForm = ({ onAdd, onCancel }: AddUserFormProps) => {
value={formData.username} value={formData.username}
onChange={handleInputChange} onChange={handleInputChange}
placeholder={t('users.usernamePlaceholder')} placeholder={t('users.usernamePlaceholder')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200"
required required
disabled={isSubmitting} disabled={isSubmitting}
/> />
@@ -95,7 +95,7 @@ const AddUserForm = ({ onAdd, onCancel }: AddUserFormProps) => {
<div> <div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1"> <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
{t('users.password')} * {t('users.password')} <span className="text-red-500">*</span>
</label> </label>
<input <input
type="password" type="password"
@@ -104,43 +104,68 @@ const AddUserForm = ({ onAdd, onCancel }: AddUserFormProps) => {
value={formData.password} value={formData.password}
onChange={handleInputChange} onChange={handleInputChange}
placeholder={t('users.passwordPlaceholder')} placeholder={t('users.passwordPlaceholder')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200"
required required
disabled={isSubmitting} disabled={isSubmitting}
minLength={6} minLength={6}
/> />
</div> </div>
<div className="flex items-center"> <div className="flex items-center pt-2">
<input <input
type="checkbox" type="checkbox"
id="isAdmin" id="isAdmin"
name="isAdmin" name="isAdmin"
checked={formData.isAdmin} checked={formData.isAdmin}
onChange={handleInputChange} onChange={handleInputChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<label htmlFor="isAdmin" className="ml-2 block text-sm text-gray-700"> <label
htmlFor="isAdmin"
className="ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none"
>
{t('users.adminRole')} {t('users.adminRole')}
</label> </label>
</div> </div>
</div> </div>
<div className="flex justify-end space-x-3 mt-6"> <div className="flex justify-end space-x-3 mt-8">
<button <button
type="button" type="button"
onClick={onCancel} onClick={onCancel}
className="px-4 py-2 text-gray-700 bg-gray-200 rounded hover:bg-gray-300 transition-colors duration-200" className="px-5 py-2.5 text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-all duration-200 font-medium btn-secondary shadow-sm"
disabled={isSubmitting} disabled={isSubmitting}
> >
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button <button
type="submit" type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" className="px-5 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200 font-medium btn-primary shadow-md disabled:opacity-70 disabled:cursor-not-allowed flex items-center"
disabled={isSubmitting} disabled={isSubmitting}
> >
{isSubmitting && (
<svg
className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
)}
{isSubmitting ? t('common.creating') : t('users.create')} {isSubmitting ? t('common.creating') : t('users.create')}
</button> </button>
</div> </div>

View File

@@ -62,93 +62,132 @@ const EditUserForm = ({ user, onEdit, onCancel }: EditUserFormProps) => {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target; const { name, value, type, checked } = e.target;
setFormData(prev => ({ setFormData((prev) => ({
...prev, ...prev,
[name]: type === 'checkbox' ? checked : value [name]: type === 'checkbox' ? checked : value,
})); }));
}; };
return ( return (
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
<div className="bg-white p-8 rounded-lg shadow-xl max-w-md w-full mx-4"> <div className="bg-white p-8 rounded-xl shadow-2xl max-w-md w-full mx-4 border border-gray-100">
<form onSubmit={handleSubmit}> <form onSubmit={handleSubmit}>
<h2 className="text-xl font-semibold text-gray-800 mb-4"> <h2 className="text-xl font-bold text-gray-900 mb-6">
{t('users.edit')} - {user.username} {t('users.edit')} - <span className="text-blue-600">{user.username}</span>
</h2> </h2>
{error && ( {error && (
<div className="bg-red-100 border-l-4 border-red-500 text-red-700 p-3 mb-4"> <div className="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 rounded-md">
<p className="text-sm">{error}</p> <p className="text-sm font-medium">{error}</p>
</div> </div>
)} )}
<div className="space-y-4"> <div className="space-y-5">
<div className="flex items-center"> <div className="flex items-center pt-2">
<input <input
type="checkbox" type="checkbox"
id="isAdmin" id="isAdmin"
name="isAdmin" name="isAdmin"
checked={formData.isAdmin} checked={formData.isAdmin}
onChange={handleInputChange} onChange={handleInputChange}
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" className="h-5 w-5 text-blue-600 focus:ring-blue-500 border-gray-300 rounded transition-colors duration-200"
disabled={isSubmitting} disabled={isSubmitting}
/> />
<label htmlFor="isAdmin" className="ml-2 block text-sm text-gray-700"> <label
htmlFor="isAdmin"
className="ml-3 block text-sm font-medium text-gray-700 cursor-pointer select-none"
>
{t('users.adminRole')} {t('users.adminRole')}
</label> </label>
</div> </div>
<div> <div className="border-t border-gray-100 pt-4 mt-2">
<label htmlFor="newPassword" className="block text-sm font-medium text-gray-700 mb-1"> <p className="text-xs text-gray-500 uppercase font-semibold tracking-wider mb-3">
{t('users.newPassword')} {t('users.changePassword')}
</label> </p>
<input
type="password"
id="newPassword"
name="newPassword"
value={formData.newPassword}
onChange={handleInputChange}
placeholder={t('users.newPasswordPlaceholder')}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
disabled={isSubmitting}
minLength={6}
/>
</div>
{formData.newPassword && ( <div className="space-y-4">
<div> <div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1"> <label
{t('users.confirmPassword')} htmlFor="newPassword"
</label> className="block text-sm font-medium text-gray-700 mb-1"
<input >
type="password" {t('users.newPassword')}
id="confirmPassword" </label>
name="confirmPassword" <input
value={formData.confirmPassword} type="password"
onChange={handleInputChange} id="newPassword"
placeholder={t('users.confirmPasswordPlaceholder')} name="newPassword"
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" value={formData.newPassword}
disabled={isSubmitting} onChange={handleInputChange}
minLength={6} placeholder={t('users.newPasswordPlaceholder')}
/> className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200"
disabled={isSubmitting}
minLength={6}
/>
</div>
{formData.newPassword && (
<div className="animate-fadeIn">
<label
htmlFor="confirmPassword"
className="block text-sm font-medium text-gray-700 mb-1"
>
{t('users.confirmPassword')}
</label>
<input
type="password"
id="confirmPassword"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleInputChange}
placeholder={t('users.confirmPasswordPlaceholder')}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent form-input transition-all duration-200"
disabled={isSubmitting}
minLength={6}
/>
</div>
)}
</div> </div>
)} </div>
</div> </div>
<div className="flex justify-end space-x-3 mt-6"> <div className="flex justify-end space-x-3 mt-8">
<button <button
type="button" type="button"
onClick={onCancel} onClick={onCancel}
className="px-4 py-2 text-gray-700 bg-gray-200 rounded hover:bg-gray-300 transition-colors duration-200" className="px-5 py-2.5 text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 transition-all duration-200 font-medium btn-secondary shadow-sm"
disabled={isSubmitting} disabled={isSubmitting}
> >
{t('common.cancel')} {t('common.cancel')}
</button> </button>
<button <button
type="submit" type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed" className="px-5 py-2.5 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200 font-medium btn-primary shadow-md disabled:opacity-70 disabled:cursor-not-allowed flex items-center"
disabled={isSubmitting} disabled={isSubmitting}
> >
{isSubmitting && (
<svg
className="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
)}
{isSubmitting ? t('common.updating') : t('users.update')} {isSubmitting ? t('common.updating') : t('users.update')}
</button> </button>
</div> </div>

View File

@@ -5,7 +5,8 @@ import { useUserData } from '@/hooks/useUserData';
import { useAuth } from '@/contexts/AuthContext'; import { useAuth } from '@/contexts/AuthContext';
import AddUserForm from '@/components/AddUserForm'; import AddUserForm from '@/components/AddUserForm';
import EditUserForm from '@/components/EditUserForm'; import EditUserForm from '@/components/EditUserForm';
import UserCard from '@/components/UserCard'; import { Edit, Trash, User as UserIcon } from 'lucide-react';
import DeleteDialog from '@/components/ui/DeleteDialog';
const UsersPage: React.FC = () => { const UsersPage: React.FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -22,11 +23,12 @@ const UsersPage: React.FC = () => {
const [editingUser, setEditingUser] = useState<User | null>(null); const [editingUser, setEditingUser] = useState<User | null>(null);
const [showAddForm, setShowAddForm] = useState(false); const [showAddForm, setShowAddForm] = useState(false);
const [userToDelete, setUserToDelete] = useState<string | null>(null);
// Check if current user is admin // Check if current user is admin
if (!currentUser?.isAdmin) { if (!currentUser?.isAdmin) {
return ( return (
<div className="bg-white shadow rounded-lg p-6"> <div className="bg-white shadow rounded-lg p-6 dashboard-card">
<p className="text-red-600">{t('users.adminRequired')}</p> <p className="text-red-600">{t('users.adminRequired')}</p>
</div> </div>
); );
@@ -41,10 +43,17 @@ const UsersPage: React.FC = () => {
triggerRefresh(); // Refresh the users list after editing triggerRefresh(); // Refresh the users list after editing
}; };
const handleDeleteUser = async (username: string) => { const handleDeleteClick = (username: string) => {
const result = await deleteUser(username); setUserToDelete(username);
if (!result?.success) { };
setUserError(result?.message || t('users.deleteError'));
const handleConfirmDelete = async () => {
if (userToDelete) {
const result = await deleteUser(userToDelete);
if (!result?.success) {
setUserError(result?.message || t('users.deleteError'));
}
setUserToDelete(null);
} }
}; };
@@ -58,13 +67,13 @@ const UsersPage: React.FC = () => {
}; };
return ( return (
<div> <div className="container mx-auto">
<div className="flex justify-between items-center mb-8"> <div className="flex justify-between items-center mb-8">
<h1 className="text-2xl font-bold text-gray-900">{t('pages.users.title')}</h1> <h1 className="text-2xl font-bold text-gray-900">{t('pages.users.title')}</h1>
<div className="flex space-x-4"> <div className="flex space-x-4">
<button <button
onClick={handleAddUser} onClick={handleAddUser}
className="px-4 py-2 bg-blue-100 text-blue-800 rounded hover:bg-blue-200 flex items-center btn-primary transition-all duration-200" className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex items-center btn-primary transition-all duration-200 shadow-sm"
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M10 3a1 1 0 00-1 1v5H4a1 1 0 100 2h5v5a1 1 0 102 0v-5h5a1 1 0 100-2h-5V4a1 1 0 00-1-1z" clipRule="evenodd" /> <path fillRule="evenodd" d="M10 3a1 1 0 00-1 1v5H4a1 1 0 100 2h5v5a1 1 0 102 0v-5h5a1 1 0 100-2h-5V4a1 1 0 00-1-1z" clipRule="evenodd" />
@@ -75,13 +84,23 @@ const UsersPage: React.FC = () => {
</div> </div>
{userError && ( {userError && (
<div className="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-6 error-box rounded-lg"> <div className="bg-red-50 border-l-4 border-red-500 text-red-700 p-4 mb-6 error-box rounded-lg shadow-sm">
<p>{userError}</p> <div className="flex justify-between items-center">
<p>{userError}</p>
<button
onClick={() => setUserError(null)}
className="text-red-500 hover:text-red-700"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4.293 4.293a1 1 011.414 0L10 8.586l4.293-4.293a1 1 111.414 1.414L11.414 10l4.293 4.293a1 1 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 01-1.414-1.414L8.586 10 4.293 5.707a1 1 010-1.414z" clipRule="evenodd" />
</svg>
</button>
</div>
</div> </div>
)} )}
{usersLoading ? ( {usersLoading ? (
<div className="bg-white shadow rounded-lg p-6 loading-container"> <div className="bg-white shadow rounded-lg p-6 loading-container flex justify-center items-center h-64">
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
<svg className="animate-spin h-10 w-10 text-blue-500 mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"> <svg className="animate-spin h-10 w-10 text-blue-500 mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
@@ -91,20 +110,93 @@ const UsersPage: React.FC = () => {
</div> </div>
</div> </div>
) : users.length === 0 ? ( ) : users.length === 0 ? (
<div className="bg-white shadow rounded-lg p-6 empty-state"> <div className="bg-white shadow rounded-lg p-6 empty-state dashboard-card">
<p className="text-gray-600">{t('users.noUsers')}</p> <div className="flex flex-col items-center justify-center py-12">
<div className="p-4 bg-gray-100 rounded-full mb-4">
<UserIcon className="h-8 w-8 text-gray-400" />
</div>
<p className="text-gray-600 text-lg font-medium">{t('users.noUsers')}</p>
<button
onClick={handleAddUser}
className="mt-4 text-blue-600 hover:text-blue-800 font-medium"
>
{t('users.addFirst')}
</button>
</div>
</div> </div>
) : ( ) : (
<div className="space-y-6"> <div className="bg-white shadow rounded-lg overflow-hidden table-container dashboard-card">
{users.map((user) => ( <table className="min-w-full divide-y divide-gray-200">
<UserCard <thead className="bg-gray-50">
key={user.username} <tr>
user={user} <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
currentUser={currentUser} {t('users.username')}
onEdit={handleEditClick} </th>
onDelete={handleDeleteUser} <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
/> {t('users.role')}
))} </th>
<th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
{t('users.actions')}
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{users.map((user) => {
const isCurrentUser = currentUser?.username === user.username;
return (
<tr key={user.username} className="hover:bg-gray-50 transition-colors duration-150">
<td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10">
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center text-blue-600 font-bold text-lg">
{user.username.charAt(0).toUpperCase()}
</div>
</div>
<div className="ml-4">
<div className="text-sm font-medium text-gray-900 flex items-center">
{user.username}
{isCurrentUser && (
<span className="ml-2 px-2 py-0.5 text-xs bg-blue-100 text-blue-800 rounded-full border border-blue-200">
{t('users.currentUser')}
</span>
)}
</div>
</div>
</div>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 py-1 inline-flex text-xs leading-5 font-semibold rounded-full ${user.isAdmin
? 'bg-purple-100 text-purple-800 border border-purple-200'
: 'bg-gray-100 text-gray-800 border border-gray-200'
}`}>
{user.isAdmin ? t('users.admin') : t('users.user')}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div className="flex justify-end space-x-3">
<button
onClick={() => handleEditClick(user)}
className="text-blue-600 hover:text-blue-900 p-1 rounded hover:bg-blue-50 transition-colors"
title={t('users.edit')}
>
<Edit size={18} />
</button>
{!isCurrentUser && (
<button
onClick={() => handleDeleteClick(user.username)}
className="text-red-600 hover:text-red-900 p-1 rounded hover:bg-red-50 transition-colors"
title={t('users.delete')}
>
<Trash size={18} />
</button>
)}
</div>
</td>
</tr>
);
})}
</tbody>
</table>
</div> </div>
)} )}
@@ -119,6 +211,15 @@ const UsersPage: React.FC = () => {
onCancel={() => setEditingUser(null)} onCancel={() => setEditingUser(null)}
/> />
)} )}
<DeleteDialog
isOpen={!!userToDelete}
onClose={() => setUserToDelete(null)}
onConfirm={handleConfirmDelete}
serverName={userToDelete || ''}
isGroup={false}
isUser={true}
/>
</div> </div>
); );
}; };

View File

@@ -673,9 +673,13 @@
"password": "Password", "password": "Password",
"newPassword": "New Password", "newPassword": "New Password",
"confirmPassword": "Confirm Password", "confirmPassword": "Confirm Password",
"changePassword": "Change Password",
"adminRole": "Administrator", "adminRole": "Administrator",
"admin": "Admin", "admin": "Admin",
"user": "User", "user": "User",
"role": "Role",
"actions": "Actions",
"addFirst": "Add your first user",
"permissions": "Permissions", "permissions": "Permissions",
"adminPermissions": "Full system access", "adminPermissions": "Full system access",
"userPermissions": "Limited access", "userPermissions": "Limited access",

View File

@@ -673,9 +673,13 @@
"password": "Mot de passe", "password": "Mot de passe",
"newPassword": "Nouveau mot de passe", "newPassword": "Nouveau mot de passe",
"confirmPassword": "Confirmer le mot de passe", "confirmPassword": "Confirmer le mot de passe",
"changePassword": "Changer le mot de passe",
"adminRole": "Administrateur", "adminRole": "Administrateur",
"admin": "Admin", "admin": "Admin",
"user": "Utilisateur", "user": "Utilisateur",
"role": "Rôle",
"actions": "Actions",
"addFirst": "Ajoutez votre premier utilisateur",
"permissions": "Permissions", "permissions": "Permissions",
"adminPermissions": "Accès complet au système", "adminPermissions": "Accès complet au système",
"userPermissions": "Accès limité", "userPermissions": "Accès limité",

View File

@@ -673,9 +673,13 @@
"password": "Şifre", "password": "Şifre",
"newPassword": "Yeni Şifre", "newPassword": "Yeni Şifre",
"confirmPassword": "Şifreyi Onayla", "confirmPassword": "Şifreyi Onayla",
"changePassword": "Şifre Değiştir",
"adminRole": "Yönetici", "adminRole": "Yönetici",
"admin": "Yönetici", "admin": "Yönetici",
"user": "Kullanıcı", "user": "Kullanıcı",
"role": "Rol",
"actions": "Eylemler",
"addFirst": "İlk kullanıcınızı ekleyin",
"permissions": "İzinler", "permissions": "İzinler",
"adminPermissions": "Tam sistem erişimi", "adminPermissions": "Tam sistem erişimi",
"userPermissions": "Sınırlı erişim", "userPermissions": "Sınırlı erişim",

View File

@@ -675,9 +675,13 @@
"password": "密码", "password": "密码",
"newPassword": "新密码", "newPassword": "新密码",
"confirmPassword": "确认密码", "confirmPassword": "确认密码",
"changePassword": "修改密码",
"adminRole": "管理员", "adminRole": "管理员",
"admin": "管理员", "admin": "管理员",
"user": "用户", "user": "用户",
"role": "角色",
"actions": "操作",
"addFirst": "添加第一个用户",
"permissions": "权限", "permissions": "权限",
"adminPermissions": "完全系统访问权限", "adminPermissions": "完全系统访问权限",
"userPermissions": "受限访问权限", "userPermissions": "受限访问权限",