This commit is contained in:
@@ -71,11 +71,12 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
const login = async (username: string, password: string) => {
|
||||
console.log('[AuthProvider] Attempting login...')
|
||||
try {
|
||||
const result = await query<{ login: { success: boolean; token?: string } }>(
|
||||
`${location.origin}/graphql`,
|
||||
ADMIN_LOGIN_MUTATION,
|
||||
{ email: username, password }
|
||||
)
|
||||
const result = await query<{
|
||||
login: { success: boolean; token?: string }
|
||||
}>(`${location.origin}/graphql`, ADMIN_LOGIN_MUTATION, {
|
||||
email: username,
|
||||
password
|
||||
})
|
||||
|
||||
if (result?.login?.success) {
|
||||
console.log('[AuthProvider] Login successful')
|
||||
@@ -97,22 +98,29 @@ export const AuthProvider: Component<AuthProviderProps> = (props) => {
|
||||
const logout = async () => {
|
||||
console.log('[AuthProvider] Attempting logout...')
|
||||
try {
|
||||
const result = await query<{ logout: { success: boolean } }>(
|
||||
// Сначала очищаем токены на клиенте
|
||||
clearAuthTokens()
|
||||
setIsAuthenticated(false)
|
||||
|
||||
// Затем делаем запрос на сервер
|
||||
const result = await query<{ logout: { success: boolean; message?: string } }>(
|
||||
`${location.origin}/graphql`,
|
||||
ADMIN_LOGOUT_MUTATION
|
||||
)
|
||||
|
||||
console.log('[AuthProvider] Logout response:', result)
|
||||
|
||||
if (result?.logout?.success) {
|
||||
console.log('[AuthProvider] Logout successful')
|
||||
clearAuthTokens()
|
||||
setIsAuthenticated(false)
|
||||
console.log('[AuthProvider] Logout successful:', result.logout.message)
|
||||
window.location.href = '/login'
|
||||
} else {
|
||||
console.warn('[AuthProvider] Logout was not successful:', result?.logout?.message)
|
||||
// Все равно редиректим на страницу входа
|
||||
window.location.href = '/login'
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[AuthProvider] Logout error:', error)
|
||||
// Даже при ошибке очищаем токены и редиректим
|
||||
clearAuthTokens()
|
||||
setIsAuthenticated(false)
|
||||
// При любой ошибке редиректим на страницу входа
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
390
panel/context/data.tsx
Normal file
390
panel/context/data.tsx
Normal file
@@ -0,0 +1,390 @@
|
||||
import { createContext, createEffect, createSignal, JSX, onMount, useContext } from 'solid-js'
|
||||
import {
|
||||
ADMIN_GET_ROLES_QUERY,
|
||||
GET_COMMUNITIES_QUERY,
|
||||
GET_TOPICS_BY_COMMUNITY_QUERY,
|
||||
GET_TOPICS_QUERY
|
||||
} from '../graphql/queries'
|
||||
|
||||
export interface Community {
|
||||
id: number
|
||||
name: string
|
||||
slug: string
|
||||
desc?: string
|
||||
pic?: string
|
||||
}
|
||||
|
||||
export interface Topic {
|
||||
id: number
|
||||
slug: string
|
||||
title: string
|
||||
body?: string
|
||||
pic?: string
|
||||
community: number
|
||||
parent_ids?: number[]
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
id: string
|
||||
name: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
interface DataContextType {
|
||||
// Сообщества
|
||||
communities: () => Community[]
|
||||
getCommunityById: (id: number) => Community | undefined
|
||||
getCommunityName: (id: number) => string
|
||||
selectedCommunity: () => number | null
|
||||
setSelectedCommunity: (id: number | null) => void
|
||||
|
||||
// Топики
|
||||
topics: () => Topic[]
|
||||
allTopics: () => Topic[]
|
||||
getTopicById: (id: number) => Topic | undefined
|
||||
getTopicTitle: (id: number) => string
|
||||
loadTopicsByCommunity: (communityId: number) => Promise<Topic[]>
|
||||
|
||||
// Роли
|
||||
roles: () => Role[]
|
||||
getRoleById: (id: string) => Role | undefined
|
||||
getRoleName: (id: string) => string
|
||||
|
||||
// Общие методы
|
||||
isLoading: () => boolean
|
||||
loadData: () => Promise<void>
|
||||
// biome-ignore lint/suspicious/noExplicitAny: grahphql
|
||||
queryGraphQL: (query: string, variables?: Record<string, any>) => Promise<any>
|
||||
}
|
||||
|
||||
const DataContext = createContext<DataContextType>({
|
||||
// Сообщества
|
||||
communities: () => [],
|
||||
getCommunityById: () => undefined,
|
||||
getCommunityName: () => '',
|
||||
selectedCommunity: () => null,
|
||||
setSelectedCommunity: () => {},
|
||||
|
||||
// Топики
|
||||
topics: () => [],
|
||||
allTopics: () => [],
|
||||
getTopicById: () => undefined,
|
||||
getTopicTitle: () => '',
|
||||
loadTopicsByCommunity: async () => [],
|
||||
|
||||
// Роли
|
||||
roles: () => [],
|
||||
getRoleById: () => undefined,
|
||||
getRoleName: () => '',
|
||||
|
||||
// Общие методы
|
||||
isLoading: () => false,
|
||||
loadData: async () => {},
|
||||
queryGraphQL: async () => {}
|
||||
})
|
||||
|
||||
/**
|
||||
* Ключ для сохранения выбранного сообщества в localStorage
|
||||
*/
|
||||
const COMMUNITY_STORAGE_KEY = 'admin-selected-community'
|
||||
|
||||
export function DataProvider(props: { children: JSX.Element }) {
|
||||
const [communities, setCommunities] = createSignal<Community[]>([])
|
||||
const [topics, setTopics] = createSignal<Topic[]>([])
|
||||
const [allTopics, setAllTopics] = createSignal<Topic[]>([])
|
||||
const [roles, setRoles] = createSignal<Role[]>([])
|
||||
|
||||
// Инициализация выбранного сообщества из localStorage
|
||||
const initialCommunity = (() => {
|
||||
try {
|
||||
const stored = localStorage.getItem(COMMUNITY_STORAGE_KEY)
|
||||
if (stored) {
|
||||
const communityId = Number.parseInt(stored, 10)
|
||||
return Number.isNaN(communityId) ? 1 : communityId
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('[DataProvider] Ошибка при чтении сообщества из localStorage:', e)
|
||||
}
|
||||
return 1 // По умолчанию выбираем сообщество с ID 1 (Дискурс)
|
||||
})()
|
||||
|
||||
const [selectedCommunity, setSelectedCommunity] = createSignal<number | null>(initialCommunity)
|
||||
const [isLoading, setIsLoading] = createSignal(false)
|
||||
|
||||
// Сохранение выбранного сообщества в localStorage
|
||||
const updateSelectedCommunity = (id: number | null) => {
|
||||
try {
|
||||
if (id !== null) {
|
||||
localStorage.setItem(COMMUNITY_STORAGE_KEY, id.toString())
|
||||
console.log('[DataProvider] Сохранено сообщество в localStorage:', id)
|
||||
} else {
|
||||
localStorage.removeItem(COMMUNITY_STORAGE_KEY)
|
||||
console.log('[DataProvider] Удалено сохраненное сообщество из localStorage')
|
||||
}
|
||||
setSelectedCommunity(id)
|
||||
} catch (e) {
|
||||
console.error('[DataProvider] Ошибка при сохранении сообщества в localStorage:', e)
|
||||
setSelectedCommunity(id) // Всё равно обновляем состояние
|
||||
}
|
||||
}
|
||||
|
||||
// Эффект для загрузки ролей при изменении сообщества
|
||||
createEffect(() => {
|
||||
const community = selectedCommunity()
|
||||
if (community !== null) {
|
||||
console.log('[DataProvider] Загрузка ролей для сообщества:', community)
|
||||
loadRoles(community).catch((err) => {
|
||||
console.warn('Не удалось загрузить роли для сообщества:', err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// Загрузка данных при монтировании
|
||||
onMount(() => {
|
||||
console.log('[DataProvider] Инициализация с сообществом:', initialCommunity)
|
||||
loadData().catch((err) => {
|
||||
console.error('Ошибка при начальной загрузке данных:', err)
|
||||
})
|
||||
})
|
||||
|
||||
// Загрузка сообществ
|
||||
const loadCommunities = async () => {
|
||||
try {
|
||||
const response = await fetch('/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: GET_COMMUNITIES_QUERY
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
|
||||
const communitiesData = result.data.get_communities_all || []
|
||||
setCommunities(communitiesData)
|
||||
return communitiesData
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки сообществ:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка всех топиков
|
||||
const loadTopics = async () => {
|
||||
try {
|
||||
const response = await fetch('/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: GET_TOPICS_QUERY
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
|
||||
const topicsData = result.data.get_topics_all || []
|
||||
setTopics(topicsData)
|
||||
return topicsData
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки топиков:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка всех топиков сообщества
|
||||
const loadTopicsByCommunity = async (communityId: number) => {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
|
||||
// Загружаем все топики сообщества сразу с лимитом 800
|
||||
const response = await fetch('/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: GET_TOPICS_BY_COMMUNITY_QUERY,
|
||||
variables: {
|
||||
community_id: communityId,
|
||||
limit: 800,
|
||||
offset: 0
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
|
||||
const allTopicsData = result.data.get_topics_by_community || []
|
||||
|
||||
// Сохраняем все данные сразу для отображения
|
||||
setTopics(allTopicsData)
|
||||
setAllTopics(allTopicsData)
|
||||
|
||||
return allTopicsData
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки топиков по сообществу:', error)
|
||||
return []
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка ролей для конкретного сообщества
|
||||
const loadRoles = async (communityId?: number) => {
|
||||
try {
|
||||
console.log(
|
||||
'[DataProvider] Загружаем роли...',
|
||||
communityId ? `для сообщества ${communityId}` : 'все роли'
|
||||
)
|
||||
|
||||
const variables = communityId ? { community: communityId } : {}
|
||||
|
||||
const response = await fetch('/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query: ADMIN_GET_ROLES_QUERY,
|
||||
variables
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
console.log('[DataProvider] Ответ от сервера для ролей:', result)
|
||||
|
||||
if (result.errors) {
|
||||
console.warn('Не удалось загрузить роли (возможно не авторизован):', result.errors[0].message)
|
||||
setRoles([])
|
||||
return []
|
||||
}
|
||||
|
||||
const rolesData = result.data.adminGetRoles || []
|
||||
console.log('[DataProvider] Роли успешно загружены:', rolesData)
|
||||
setRoles(rolesData)
|
||||
return rolesData
|
||||
} catch (error) {
|
||||
console.warn('Ошибка загрузки ролей:', error)
|
||||
setRoles([])
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка всех данных
|
||||
const loadData = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
// Загружаем все данные сразу (вызывается только для авторизованных пользователей)
|
||||
// Роли загружаем в фоне - их отсутствие не должно блокировать интерфейс
|
||||
await Promise.all([
|
||||
loadCommunities(),
|
||||
loadTopics(),
|
||||
loadRoles(selectedCommunity() || undefined).catch((err) => {
|
||||
console.warn('Роли недоступны (возможно не хватает прав):', err)
|
||||
return []
|
||||
})
|
||||
])
|
||||
|
||||
// selectedCommunity теперь всегда инициализировано со значением 1,
|
||||
// поэтому дополнительная проверка не нужна
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки данных:', error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Методы для работы с сообществами
|
||||
const getCommunityById = (id: number): Community | undefined => {
|
||||
return communities().find((community) => community.id === id)
|
||||
}
|
||||
|
||||
const getCommunityName = (id: number): string => getCommunityById(id)?.name || ''
|
||||
const getTopicTitle = (id: number): string => getTopicById(id)?.title || ''
|
||||
|
||||
// Методы для работы с топиками
|
||||
const getTopicById = (id: number): Topic | undefined => {
|
||||
return topics().find((topic) => topic.id === id)
|
||||
}
|
||||
|
||||
// Методы для работы с ролями
|
||||
const getRoleById = (id: string): Role | undefined => {
|
||||
return roles().find((role) => role.id === id)
|
||||
}
|
||||
|
||||
const getRoleName = (id: string): string => {
|
||||
const role = getRoleById(id)
|
||||
return role ? role.name : id
|
||||
}
|
||||
|
||||
const value = {
|
||||
// Сообщества
|
||||
communities,
|
||||
getCommunityById,
|
||||
getCommunityName,
|
||||
selectedCommunity,
|
||||
setSelectedCommunity: updateSelectedCommunity,
|
||||
|
||||
// Топики
|
||||
topics,
|
||||
allTopics,
|
||||
getTopicById,
|
||||
getTopicTitle,
|
||||
loadTopicsByCommunity,
|
||||
|
||||
// Роли
|
||||
roles,
|
||||
getRoleById,
|
||||
getRoleName,
|
||||
|
||||
// Общие методы
|
||||
isLoading,
|
||||
loadData,
|
||||
// biome-ignore lint/suspicious/noExplicitAny: grahphql
|
||||
queryGraphQL: async (query: string, variables?: Record<string, any>) => {
|
||||
try {
|
||||
const response = await fetch('/graphql', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables
|
||||
})
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
|
||||
if (result.errors) {
|
||||
throw new Error(result.errors[0].message)
|
||||
}
|
||||
|
||||
return result.data
|
||||
} catch (error) {
|
||||
console.error('Ошибка выполнения GraphQL запроса:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return <DataContext.Provider value={value}>{props.children}</DataContext.Provider>
|
||||
}
|
||||
|
||||
export const useData = () => useContext(DataContext)
|
150
panel/context/sort.tsx
Normal file
150
panel/context/sort.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { createContext, createSignal, ParentComponent, useContext } from 'solid-js'
|
||||
|
||||
/**
|
||||
* Типы полей сортировки для разных вкладок
|
||||
*/
|
||||
export type AuthorsSortField = 'id' | 'email' | 'name' | 'created_at' | 'last_seen'
|
||||
export type ShoutsSortField = 'id' | 'title' | 'slug' | 'created_at' | 'published_at' | 'updated_at'
|
||||
export type TopicsSortField =
|
||||
| 'id'
|
||||
| 'title'
|
||||
| 'slug'
|
||||
| 'created_at'
|
||||
| 'authors'
|
||||
| 'shouts'
|
||||
| 'followers'
|
||||
| 'authors'
|
||||
export type CommunitiesSortField =
|
||||
| 'id'
|
||||
| 'name'
|
||||
| 'slug'
|
||||
| 'created_at'
|
||||
| 'created_by'
|
||||
| 'shouts'
|
||||
| 'followers'
|
||||
| 'authors'
|
||||
export type CollectionsSortField = 'id' | 'title' | 'slug' | 'created_at' | 'published_at'
|
||||
export type InvitesSortField = 'inviter_name' | 'author_name' | 'shout_title' | 'status'
|
||||
|
||||
/**
|
||||
* Общий тип для всех полей сортировки
|
||||
*/
|
||||
export type SortField =
|
||||
| AuthorsSortField
|
||||
| ShoutsSortField
|
||||
| TopicsSortField
|
||||
| CommunitiesSortField
|
||||
| CollectionsSortField
|
||||
| InvitesSortField
|
||||
|
||||
/**
|
||||
* Направление сортировки
|
||||
*/
|
||||
export type SortDirection = 'asc' | 'desc'
|
||||
|
||||
/**
|
||||
* Состояние сортировки
|
||||
*/
|
||||
export interface SortState {
|
||||
field: SortField
|
||||
direction: SortDirection
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для разных вкладок
|
||||
*/
|
||||
export interface TabSortConfig {
|
||||
allowedFields: SortField[]
|
||||
defaultField: SortField
|
||||
defaultDirection: SortDirection
|
||||
}
|
||||
|
||||
/**
|
||||
* Контекст для управления сортировкой таблиц
|
||||
*/
|
||||
interface TableSortContextType {
|
||||
sortState: () => SortState
|
||||
setSortState: (state: SortState) => void
|
||||
handleSort: (field: SortField, allowedFields?: SortField[]) => void
|
||||
getSortIcon: (field: SortField) => string
|
||||
isFieldAllowed: (field: SortField, allowedFields?: SortField[]) => boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаем контекст
|
||||
*/
|
||||
const TableSortContext = createContext<TableSortContextType>()
|
||||
|
||||
/**
|
||||
* Провайдер контекста сортировки
|
||||
*/
|
||||
export const TableSortProvider: ParentComponent = (props) => {
|
||||
// Состояние сортировки - по умолчанию сортировка по ID по возрастанию
|
||||
const [sortState, setSortState] = createSignal<SortState>({
|
||||
field: 'id',
|
||||
direction: 'asc'
|
||||
})
|
||||
|
||||
/**
|
||||
* Проверяет, разрешено ли поле для сортировки
|
||||
*/
|
||||
const isFieldAllowed = (field: SortField, allowedFields?: SortField[]) => {
|
||||
if (!allowedFields) return true
|
||||
return allowedFields.includes(field)
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик клика по заголовку колонки для сортировки
|
||||
*/
|
||||
const handleSort = (field: SortField, allowedFields?: SortField[]) => {
|
||||
// Проверяем, разрешено ли поле для сортировки
|
||||
if (!isFieldAllowed(field, allowedFields)) {
|
||||
console.warn(`Поле ${field} не разрешено для сортировки`)
|
||||
return
|
||||
}
|
||||
|
||||
const current = sortState()
|
||||
let newDirection: SortDirection = 'asc'
|
||||
|
||||
if (current.field === field) {
|
||||
// Если кликнули по той же колонке, меняем направление
|
||||
newDirection = current.direction === 'asc' ? 'desc' : 'asc'
|
||||
}
|
||||
|
||||
const newState = { field, direction: newDirection }
|
||||
console.log('Изменение сортировки:', { from: current, to: newState })
|
||||
setSortState(newState)
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает иконку сортировки для колонки
|
||||
*/
|
||||
const getSortIcon = (field: SortField) => {
|
||||
const current = sortState()
|
||||
if (current.field !== field) {
|
||||
return '⇅' // Неактивная сортировка
|
||||
}
|
||||
return current.direction === 'asc' ? '▲' : '▼'
|
||||
}
|
||||
|
||||
const contextValue: TableSortContextType = {
|
||||
sortState,
|
||||
setSortState,
|
||||
handleSort,
|
||||
getSortIcon,
|
||||
isFieldAllowed
|
||||
}
|
||||
|
||||
return <TableSortContext.Provider value={contextValue}>{props.children}</TableSortContext.Provider>
|
||||
}
|
||||
|
||||
/**
|
||||
* Хук для использования контекста сортировки
|
||||
*/
|
||||
export const useTableSort = () => {
|
||||
const context = useContext(TableSortContext)
|
||||
if (!context) {
|
||||
throw new Error('useTableSort должен использоваться внутри TableSortProvider')
|
||||
}
|
||||
return context
|
||||
}
|
142
panel/context/sortConfig.ts
Normal file
142
panel/context/sortConfig.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import type {
|
||||
AuthorsSortField,
|
||||
CollectionsSortField,
|
||||
CommunitiesSortField,
|
||||
InvitesSortField,
|
||||
ShoutsSortField,
|
||||
TabSortConfig,
|
||||
TopicsSortField
|
||||
} from './sort'
|
||||
|
||||
/**
|
||||
* Конфигурации сортировки для разных вкладок админ-панели
|
||||
* Основаны на том, что реально поддерживают резолверы в бэкенде
|
||||
*/
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для вкладки "Авторы"
|
||||
* Основана на резолвере admin_get_users в resolvers/admin.py
|
||||
*/
|
||||
export const AUTHORS_SORT_CONFIG: TabSortConfig = {
|
||||
allowedFields: ['id', 'email', 'name', 'created_at', 'last_seen'] as AuthorsSortField[],
|
||||
defaultField: 'id' as AuthorsSortField,
|
||||
defaultDirection: 'asc'
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для вкладки "Публикации"
|
||||
* Основана на резолвере admin_get_shouts в resolvers/admin.py
|
||||
*/
|
||||
export const SHOUTS_SORT_CONFIG: TabSortConfig = {
|
||||
allowedFields: ['id', 'title', 'slug', 'created_at', 'published_at', 'updated_at'] as ShoutsSortField[],
|
||||
defaultField: 'id' as ShoutsSortField,
|
||||
defaultDirection: 'desc' // Новые публикации сначала
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для вкладки "Темы"
|
||||
* Основана на резолвере get_topics_with_stats в resolvers/topic.py
|
||||
*/
|
||||
export const TOPICS_SORT_CONFIG: TabSortConfig = {
|
||||
allowedFields: [
|
||||
'id',
|
||||
'title',
|
||||
'slug',
|
||||
'created_at',
|
||||
'authors',
|
||||
'shouts',
|
||||
'followers'
|
||||
] as TopicsSortField[],
|
||||
defaultField: 'id' as TopicsSortField,
|
||||
defaultDirection: 'asc'
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для вкладки "Сообщества"
|
||||
* Основана на резолвере get_communities_all в resolvers/community.py
|
||||
*/
|
||||
export const COMMUNITIES_SORT_CONFIG: TabSortConfig = {
|
||||
allowedFields: [
|
||||
'id',
|
||||
'name',
|
||||
'slug',
|
||||
'created_at',
|
||||
'created_by',
|
||||
'shouts',
|
||||
'followers',
|
||||
'authors'
|
||||
] as CommunitiesSortField[],
|
||||
defaultField: 'id' as CommunitiesSortField,
|
||||
defaultDirection: 'asc'
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для вкладки "Коллекции"
|
||||
* Основана на резолвере get_collections_all в resolvers/collection.py
|
||||
*/
|
||||
export const COLLECTIONS_SORT_CONFIG: TabSortConfig = {
|
||||
allowedFields: ['id', 'title', 'slug', 'created_at', 'published_at'] as CollectionsSortField[],
|
||||
defaultField: 'id' as CollectionsSortField,
|
||||
defaultDirection: 'asc'
|
||||
}
|
||||
|
||||
/**
|
||||
* Конфигурация сортировки для вкладки "Приглашения"
|
||||
* Основана на резолвере admin_get_invites в resolvers/admin.py
|
||||
*/
|
||||
export const INVITES_SORT_CONFIG: TabSortConfig = {
|
||||
allowedFields: ['inviter_name', 'author_name', 'shout_title', 'status'] as InvitesSortField[],
|
||||
defaultField: 'inviter_name' as InvitesSortField,
|
||||
defaultDirection: 'asc'
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает конфигурацию сортировки для указанной вкладки
|
||||
*/
|
||||
export const getSortConfigForTab = (tab: string): TabSortConfig => {
|
||||
switch (tab) {
|
||||
case 'authors':
|
||||
return AUTHORS_SORT_CONFIG
|
||||
case 'shouts':
|
||||
return SHOUTS_SORT_CONFIG
|
||||
case 'topics':
|
||||
return TOPICS_SORT_CONFIG
|
||||
case 'communities':
|
||||
return COMMUNITIES_SORT_CONFIG
|
||||
case 'collections':
|
||||
return COLLECTIONS_SORT_CONFIG
|
||||
case 'invites':
|
||||
return INVITES_SORT_CONFIG
|
||||
default:
|
||||
// По умолчанию возвращаем конфигурацию авторов
|
||||
return AUTHORS_SORT_CONFIG
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Переводы названий полей для отображения пользователю
|
||||
*/
|
||||
export const FIELD_LABELS: Record<string, string> = {
|
||||
// Общие поля
|
||||
id: 'ID',
|
||||
title: 'Название',
|
||||
name: 'Имя',
|
||||
slug: 'Slug',
|
||||
created_at: 'Создано',
|
||||
updated_at: 'Обновлено',
|
||||
published_at: 'Опубликовано',
|
||||
created_by: 'Создатель',
|
||||
shouts: 'Публикации',
|
||||
followers: 'Подписчики',
|
||||
authors: 'Авторы',
|
||||
|
||||
// Поля авторов
|
||||
email: 'Email',
|
||||
last_seen: 'Последний вход',
|
||||
|
||||
// Поля приглашений
|
||||
inviter_name: 'Приглашающий',
|
||||
author_name: 'Приглашаемый',
|
||||
shout_title: 'Публикация',
|
||||
status: 'Статус'
|
||||
}
|
Reference in New Issue
Block a user