import { Component, createSignal, For, onMount, Show } from 'solid-js' import { ADMIN_CREATE_INVITE_MUTATION, ADMIN_DELETE_INVITE_MUTATION, ADMIN_UPDATE_INVITE_MUTATION } from '../graphql/mutations' import { ADMIN_GET_INVITES_QUERY } from '../graphql/queries' import InviteEditModal from '../modals/InviteEditModal' import styles from '../styles/Table.module.css' import Button from '../ui/Button' import Modal from '../ui/Modal' import Pagination from '../ui/Pagination' /** * Интерфейсы для приглашений */ interface Author { id: number name: string email: string slug: string } interface Shout { id: number title: string slug: string created_by: Author } interface Invite { inviter_id: number author_id: number shout_id: number status: 'PENDING' | 'ACCEPTED' | 'REJECTED' inviter: Author author: Author shout: Shout created_at?: number } interface InvitesRouteProps { onError: (error: string) => void onSuccess: (message: string) => void } /** * Компонент для управления приглашениями */ const InvitesRoute: Component = (props) => { const [invites, setInvites] = createSignal([]) const [loading, setLoading] = createSignal(false) const [search, setSearch] = createSignal('') const [statusFilter, setStatusFilter] = createSignal('all') const [pagination, setPagination] = createSignal({ page: 1, perPage: 10, total: 0, totalPages: 1 }) const [editModal, setEditModal] = createSignal<{ show: boolean; invite: Invite | null }>({ show: false, invite: null }) const [deleteModal, setDeleteModal] = createSignal<{ show: boolean; invite: Invite | null }>({ show: false, invite: null }) const [createModal, setCreateModal] = createSignal<{ show: boolean }>({ show: false }) /** * Загружает список приглашений с учетом фильтров и пагинации */ const loadInvites = async (page: number = 1) => { setLoading(true) try { const limit = pagination().perPage const offset = (page - 1) * limit const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ADMIN_GET_INVITES_QUERY, variables: { limit, offset, search: search().trim() || null, status: statusFilter() === 'all' ? null : statusFilter() } }) }) const result = await response.json() if (result.errors) { throw new Error(result.errors[0].message) } const data = result.data.adminGetInvites setInvites(data.invites || []) setPagination({ page: data.page || 1, perPage: data.perPage || 10, total: data.total || 0, totalPages: data.totalPages || 1 }) } catch (error) { props.onError(`Ошибка загрузки приглашений: ${(error as Error).message}`) } finally { setLoading(false) } } /** * Обработчик изменения страницы */ const handlePageChange = (page: number) => { void loadInvites(page) } /** * Обработчик поиска */ const handleSearch = () => { void loadInvites(1) // Сброс на первую страницу при поиске } /** * Обработчик изменения фильтра статуса */ const handleStatusFilterChange = (status: string) => { setStatusFilter(status) void loadInvites(1) } /** * Получает отображаемое название статуса */ const getStatusDisplay = (status: string) => { switch (status) { case 'PENDING': return { text: 'Ожидает', badge: 'warning' } case 'ACCEPTED': return { text: 'Принято', badge: 'success' } case 'REJECTED': return { text: 'Отклонено', badge: 'error' } default: return { text: status, badge: 'secondary' } } } /** * Открывает модалку создания */ const openCreateModal = () => { setCreateModal({ show: true }) } /** * Открывает модалку редактирования */ const openEditModal = (invite: Invite) => { setEditModal({ show: true, invite }) } /** * Обрабатывает сохранение приглашения (создание или обновление) */ const handleSaveInvite = async (inviteData: Partial) => { try { const isCreating = !editModal().invite && createModal().show const mutation = isCreating ? ADMIN_CREATE_INVITE_MUTATION : ADMIN_UPDATE_INVITE_MUTATION const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: mutation, variables: { invite: inviteData } }) }) const result = await response.json() if (result.errors) { throw new Error(result.errors[0].message) } const resultData = isCreating ? result.data.adminCreateInvite : result.data.adminUpdateInvite if (!resultData.success) { throw new Error(resultData.error || 'Неизвестная ошибка') } props.onSuccess(isCreating ? 'Приглашение успешно создано' : 'Приглашение успешно обновлено') setCreateModal({ show: false }) setEditModal({ show: false, invite: null }) await loadInvites(pagination().page) } catch (error) { props.onError( `Ошибка ${createModal().show ? 'создания' : 'обновления'} приглашения: ${(error as Error).message}` ) } } /** * Удаляет приглашение */ const deleteInvite = async (invite: Invite) => { try { const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: ADMIN_DELETE_INVITE_MUTATION, variables: { inviter_id: invite.inviter_id, author_id: invite.author_id, shout_id: invite.shout_id } }) }) const result = await response.json() if (result.errors) { throw new Error(result.errors[0].message) } if (!result.data.adminDeleteInvite.success) { throw new Error(result.data.adminDeleteInvite.error || 'Неизвестная ошибка') } props.onSuccess('Приглашение успешно удалено') setDeleteModal({ show: false, invite: null }) await loadInvites(pagination().page) } catch (error) { props.onError(`Ошибка удаления приглашения: ${(error as Error).message}`) } } // Загружаем приглашения при монтировании компонента onMount(() => { void loadInvites() }) return (
setSearch(e.target.value)} onKeyPress={(e) => e.key === 'Enter' && handleSearch()} class={styles.searchInput} />
Загрузка приглашений...
Приглашения не найдены
0}>
{(invite) => { const statusDisplay = getStatusDisplay(invite.status) return ( openEditModal(invite)} title="Нажмите для редактирования" > ) }}
Приглашающий Приглашаемый Публикация Статус Действия
{invite.inviter.name || 'Без имени'}
{invite.inviter.email}
ID: {invite.inviter_id}
{invite.author.name || 'Без имени'}
{invite.author.email}
ID: {invite.author_id}
{invite.shout.title}
Автор: {invite.shout.created_by.name}
ID: {invite.shout_id}
{statusDisplay.text}
{/* Модальные окна */} setCreateModal({ show: false })} onSave={handleSaveInvite} /> setEditModal({ show: false, invite: null })} onSave={handleSaveInvite} /> {/* Модальное окно подтверждения удаления */} setDeleteModal({ show: false, invite: null })} title="Подтверждение удаления" size="small" >

Вы действительно хотите удалить приглашение от{' '} {deleteModal().invite?.inviter.name} для{' '} {deleteModal().invite?.author.name} к публикации{' '} "{deleteModal().invite?.shout.title}"?

) } export default InvitesRoute