Добавлен фильтр списка статей по пользователю. Админ видит все.

This commit is contained in:
anibilag 2025-03-03 23:18:43 +03:00
parent e254ef0dc0
commit 00376c124f
5 changed files with 37 additions and 15 deletions

View File

@ -36,7 +36,7 @@ export function AuthGuard({ children, requiredPermissions }: AuthGuardProps) {
<div className="text-center">
<h2 className="text-2xl font-bold text-gray-900 mb-4">Access Denied</h2>
<p className="text-gray-600">
You don't have permission to {action} articles in the {categoryId} category.
У вас нет прав на {action} статьи в {categoryId} категории.
</p>
</div>
</div>

View File

@ -16,7 +16,8 @@ import {
AlignCenter,
Plus,
Minus,
SquareUser
SquareUser,
ImagePlus
} from 'lucide-react';
import { useEffect, useState } from "react";
@ -339,6 +340,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
className={`p-1 rounded hover:bg-gray-200 ${
editor?.isActive('bold') ? 'bg-gray-200' : ''
}`}
title="Выделение слов жирным шрифтом"
>
<Bold size={18} />
</button>
@ -346,6 +348,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
type="button"
onClick={handleClick}
className="p-1 rounded hover:bg-gray-200"
title="Автоматическое выделение жирным имен"
>
<SquareUser size={18} />
</button>
@ -355,6 +358,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
className={`p-1 rounded hover:bg-gray-200 ${
editor?.isActive('italic') ? 'bg-gray-200' : ''
}`}
title="Выделение слов наклонным шрифтом"
>
<Italic size={18} />
</button>
@ -362,6 +366,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
type="button"
onClick={() => editor?.chain().focus().setParagraph().run()}
className={`p-1 rounded hover:bg-gray-200 ${editor?.isActive('paragraph') ? 'bg-gray-200' : ''}`}
title="Выравнивание по левому краю"
>
<AlignLeft size={18} />
</button>
@ -370,6 +375,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
onClick={() => editor?.chain().focus().undo().run()}
disabled={!editor?.can().chain().focus().undo().run()}
className="p-1 rounded hover:bg-gray-200 disabled:opacity-50"
title="Отменить действие"
>
<Undo size={18} />
</button>
@ -378,6 +384,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
onClick={() => editor?.chain().focus().redo().run()}
disabled={!editor?.can().chain().focus().redo().run()}
className="p-1 rounded hover:bg-gray-200 disabled:opacity-50"
title="Вернуть действие"
>
<Redo size={18} />
</button>
@ -387,6 +394,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
className={`p-1 rounded hover:bg-gray-200 ${
editor?.isActive('bulletList') ? 'bg-gray-200' : ''
}`}
title="Создать список"
>
<List size={18} />
</button>
@ -396,6 +404,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
className={`p-1 rounded hover:bg-gray-200 ${
editor?.isActive('orderedList') ? 'bg-gray-200' : ''
}`}
title="Создать упорядоченный список"
>
<ListOrdered size={18} />
</button>
@ -406,6 +415,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
className={`p-1 rounded hover:bg-gray-200 ${
editor?.isActive('blockquote') ? 'bg-gray-200' : ''
}`}
title="Цитирование (параграф)"
>
<Quote size={18} />
</button>
@ -416,6 +426,7 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
className={`p-1 rounded hover:bg-gray-200 ${
editor?.getAttributes('textAlign')?.textAlign === 'center' ? 'bg-gray-200' : ''
}`}
title="Выравнивание по центру"
>
<AlignCenter size={18} />
</button>
@ -427,8 +438,9 @@ export function TipTapEditor({ initialContent, onContentChange }: TipTapEditorPr
editor?.chain().focus().setImage({ src: url }).run();
}
}}
title="Вставить изображение"
>
🖼 Фото
<ImagePlus size={18} />
</button>
<button
type="button"

View File

@ -20,8 +20,8 @@ export function useUserManagement() {
setUsers(fetchedUsers);
setError(null);
} catch (err) {
setError('Failed to fetch users');
console.error('Error fetching users:', err);
setError('Ошибка получения списка пользователей');
console.error('Ошибка получения списка пользователей:', err);
} finally {
setLoading(false);
}

View File

@ -72,7 +72,9 @@ export function AdminPage() {
page: currentPage,
categoryId: filterCategoryId,
cityId: filterCityId,
isDraft: showDraftOnly
isDraft: showDraftOnly,
userId: user?.id,
isAdmin: isAdmin
}
});
setArticles(response.data.articles);
@ -90,7 +92,7 @@ export function AdminPage() {
setCurrentPage(page);
};
// Фильтр для категорий и городов, основанный на разрешениях пользователя
// Фильтр для категорий, основанный на разрешениях пользователя
const availableCategoryIds = useMemo(() => {
if (!user) return [];
if (isAdmin) return allCategoryIds;
@ -101,6 +103,7 @@ export function AdminPage() {
);
}, [user, isAdmin]);
// Фильтр для городов, основанный на разрешениях пользователя
const availableCityIds = useMemo(() => {
if (!user) return [];
if (isAdmin) return allCityIds;
@ -309,7 +312,7 @@ export function AdminPage() {
{editingId ? 'Редактировать статью' : 'Создать новую статью'}
</h1>
{error && (
{error && isAdmin && (
<div className="mb-6 bg-red-50 text-red-700 p-4 rounded-md">
{error}
</div>
@ -581,7 +584,7 @@ export function AdminPage() {
<img
src={article.author.avatarUrl}
alt={article.author.displayName}
className="h-4 w-4 rounded-full mr-1"
className="h-6 w-6 rounded-full mr-1"
/>
<span className="italic font-bold"> {article.author.displayName}</span>
</div>

View File

@ -7,6 +7,7 @@ import { ImagePlus, X, UserPlus, Pencil } from 'lucide-react';
import { imageResolutions } from '../config/imageResolutions';
import { CategoryIds, CategoryTitles, CityIds, CityTitles } from "../types";
import axios from "axios";
import {useAuthStore} from "../stores/authStore.ts";
const initialFormData: UserFormData = {
@ -36,6 +37,8 @@ export function UserManagementPage() {
const [formData, setFormData] = useState<UserFormData>(initialFormData);
const [formError, setFormError] = useState<string | null>(null);
const { user } = useAuthStore();
const handleAvatarUpload = async (event: React.ChangeEvent<HTMLInputElement>, userId: string) => {
const file = event.target.files?.[0];
if (!file) return;
@ -78,7 +81,7 @@ export function UserManagementPage() {
}
setFormData(initialFormData);
} catch (error) {
setFormError(error instanceof Error ? error.message : 'An error occurred');
setFormError(error instanceof Error ? error.message : 'Произошла ошибка');
}
};
@ -101,11 +104,14 @@ export function UserManagementPage() {
Управление пользователями
</h1>
<button
onClick={() => setShowCreateModal(true)}
onClick={() => {
setFormData(initialFormData);
setShowCreateModal(true);
}}
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700"
>
<UserPlus className="h-5 w-5 mr-2" />
Add New User
Новый пользователь
</button>
</div>
@ -240,7 +246,7 @@ export function UserManagementPage() {
</main>
{/* Create/Edit User Modal */}
{(showCreateModal || showEditModal) && (
{((showCreateModal || showEditModal) && user?.permissions.isAdmin) && (
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 flex items-center justify-center p-4">
<div className="bg-white rounded-lg max-w-md w-full p-6">
<div className="flex justify-between items-center mb-4">
@ -318,12 +324,13 @@ export function UserManagementPage() {
<div>
<label className="block text-sm font-medium text-gray-700">
Пароль {showEditModal && '(оставить пустым чтобы использовать текущий)'}
Пароль {showEditModal}
</label>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData(prev => ({ ...prev, password: e.target.value }))}
placeholder={showEditModal ? 'Оставьте пустым, чтобы не менять' : ''}
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
required={!showEditModal}
/>