import { Component, createSignal, For, Show } from 'solid-js' import { MERGE_TOPICS_MUTATION } from '../graphql/mutations' import styles from '../styles/Form.module.css' import Button from '../ui/Button' import Modal from '../ui/Modal' // Типы для топиков interface Topic { id: number title: string slug: string community: number stat?: { shouts: number followers: number authors: number comments: number } } interface TopicMergeModalProps { isOpen: boolean onClose: () => void topics: Topic[] onSuccess: (message: string) => void onError: (error: string) => void } interface MergeStats { followers_moved: number publications_moved: number drafts_moved: number source_topics_deleted: number } const TopicMergeModal: Component = (props) => { const [targetTopicId, setTargetTopicId] = createSignal(null) const [sourceTopicIds, setSourceTopicIds] = createSignal([]) const [preserveTarget, setPreserveTarget] = createSignal(true) const [loading, setLoading] = createSignal(false) const [error, setError] = createSignal('') /** * Получает токен авторизации из localStorage или cookie */ const getAuthTokenFromCookie = () => { return ( document.cookie .split('; ') .find((row) => row.startsWith('auth_token=')) ?.split('=')[1] || '' ) } /** * Обработчик выбора/снятия выбора исходной темы */ const handleSourceTopicToggle = (topicId: number, checked: boolean) => { if (checked) { setSourceTopicIds((prev) => [...prev, topicId]) } else { setSourceTopicIds((prev) => prev.filter((id) => id !== topicId)) } } /** * Проверяет можно ли выполнить слияние */ const canMerge = () => { const target = targetTopicId() const sources = sourceTopicIds() if (!target || sources.length === 0) { return false } // Проверяем что целевая тема не выбрана как исходная if (sources.includes(target)) { return false } // Проверяем что все темы принадлежат одному сообществу const targetTopic = props.topics.find((t) => t.id === target) if (!targetTopic) return false const targetCommunity = targetTopic.community const sourcesTopics = props.topics.filter((t) => sources.includes(t.id)) return sourcesTopics.every((topic) => topic.community === targetCommunity) } /** * Получает название сообщества по ID (заглушка) */ const getCommunityName = (communityId: number) => { // Здесь можно добавить запрос к API или кеш сообществ return `Сообщество ${communityId}` } /** * Выполняет слияние топиков */ const handleMerge = async () => { if (!canMerge()) { setError('Невозможно выполнить слияние с текущими настройками') return } setLoading(true) setError('') try { const authToken = localStorage.getItem('auth_token') || getAuthTokenFromCookie() const response = await fetch('/graphql', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: authToken ? `Bearer ${authToken}` : '' }, body: JSON.stringify({ query: MERGE_TOPICS_MUTATION, variables: { merge_input: { target_topic_id: targetTopicId(), source_topic_ids: sourceTopicIds(), preserve_target_properties: preserveTarget() } } }) }) const result = await response.json() if (result.errors) { throw new Error(result.errors[0].message) } const mergeResult = result.data.merge_topics if (mergeResult.error) { throw new Error(mergeResult.error) } const stats = mergeResult.stats as MergeStats const statsText = stats ? ` (перенесено ${stats.followers_moved} подписчиков, ${stats.publications_moved} публикаций, ${stats.drafts_moved} черновиков, удалено ${stats.source_topics_deleted} тем)` : '' props.onSuccess(mergeResult.message + statsText) handleClose() } catch (error) { const errorMessage = (error as Error).message setError(errorMessage) props.onError(`Ошибка слияния тем: ${errorMessage}`) } finally { setLoading(false) } } /** * Закрывает модалку и сбрасывает состояние */ const handleClose = () => { setTargetTopicId(null) setSourceTopicIds([]) setPreserveTarget(true) setError('') setLoading(false) props.onClose() } /** * Получает отфильтрованный список топиков (исключая выбранные как исходные) */ const getAvailableTargetTopics = () => { const sources = sourceTopicIds() return props.topics.filter((topic) => !sources.includes(topic.id)) } /** * Получает отфильтрованный список топиков (исключая целевую тему) */ const getAvailableSourceTopics = () => { const target = targetTopicId() return props.topics.filter((topic) => topic.id !== target) } return (

Выбор целевой темы

Выберите тему, в которую будут слиты остальные темы. Все подписчики и публикации будут перенесены в эту тему.

Выбор исходных тем для слияния

Выберите темы, которые будут слиты в целевую тему. Эти темы будут удалены после переноса всех связей.

0}>
{(topic) => { const isChecked = () => sourceTopicIds().includes(topic.id) return ( ) }}

Настройки слияния

{error()}
0}>

Предпросмотр слияния:

  • Целевая тема: {props.topics.find((t) => t.id === targetTopicId())?.title}
  • Исходные темы: {sourceTopicIds().length} шт.
      {(id) => { const topic = props.topics.find((t) => t.id === id) return topic ?
    • {topic.title}
    • : null }}
  • Действие: Все подписчики, публикации и черновики будут перенесены в целевую тему, исходные темы будут удалены
) } export default TopicMergeModal