2025-07-03 09:15:10 +00:00
|
|
|
|
import { createEffect, createSignal, For, Show, on } from 'solid-js'
|
2025-07-02 19:30:21 +00:00
|
|
|
|
import { Topic, useData } from '../context/data'
|
2025-07-03 09:15:10 +00:00
|
|
|
|
import { query } from '../graphql'
|
|
|
|
|
import { ADMIN_UPDATE_TOPIC_MUTATION } from '../graphql/mutations'
|
2025-07-02 19:30:21 +00:00
|
|
|
|
import styles from '../styles/Form.module.css'
|
2025-06-30 18:25:26 +00:00
|
|
|
|
import Modal from '../ui/Modal'
|
2025-07-03 09:15:10 +00:00
|
|
|
|
import TopicPillsCloud, { type TopicPill } from '../ui/TopicPillsCloud'
|
|
|
|
|
import HTMLEditor from '../ui/HTMLEditor'
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
|
|
|
|
interface TopicEditModalProps {
|
2025-07-02 19:30:21 +00:00
|
|
|
|
topic: Topic
|
2025-06-30 18:25:26 +00:00
|
|
|
|
isOpen: boolean
|
|
|
|
|
onClose: () => void
|
2025-07-02 19:30:21 +00:00
|
|
|
|
onSave: (updatedTopic: Topic) => void
|
|
|
|
|
onError?: (message: string) => void
|
2025-06-30 18:25:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
export default function TopicEditModal(props: TopicEditModalProps) {
|
|
|
|
|
const { communities, topics, getCommunityName, selectedCommunity } = useData()
|
|
|
|
|
|
|
|
|
|
// Состояние формы
|
|
|
|
|
const [formData, setFormData] = createSignal({
|
2025-06-30 18:25:26 +00:00
|
|
|
|
id: 0,
|
|
|
|
|
title: '',
|
2025-07-02 19:30:21 +00:00
|
|
|
|
slug: '',
|
2025-06-30 18:25:26 +00:00
|
|
|
|
body: '',
|
|
|
|
|
community: 0,
|
2025-07-02 19:30:21 +00:00
|
|
|
|
parent_ids: [] as number[]
|
2025-06-30 18:25:26 +00:00
|
|
|
|
})
|
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
// Состояние для выбора родителей
|
|
|
|
|
const [availableParents, setAvailableParents] = createSignal<Topic[]>([])
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
const [saving, setSaving] = createSignal(false)
|
|
|
|
|
|
|
|
|
|
// Инициализация формы при открытии
|
|
|
|
|
createEffect(() => {
|
|
|
|
|
if (props.isOpen && props.topic) {
|
2025-07-03 09:15:10 +00:00
|
|
|
|
const topicCommunity = props.topic.community || selectedCommunity() || 0
|
2025-07-02 19:30:21 +00:00
|
|
|
|
setFormData({
|
|
|
|
|
id: props.topic.id,
|
|
|
|
|
title: props.topic.title || '',
|
|
|
|
|
slug: props.topic.slug || '',
|
|
|
|
|
body: props.topic.body || '',
|
2025-07-03 09:15:10 +00:00
|
|
|
|
community: topicCommunity,
|
2025-07-02 19:30:21 +00:00
|
|
|
|
parent_ids: props.topic.parent_ids || []
|
|
|
|
|
})
|
2025-07-03 09:15:10 +00:00
|
|
|
|
updateAvailableParents(topicCommunity, props.topic.id)
|
2025-06-30 18:25:26 +00:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2025-07-03 09:15:10 +00:00
|
|
|
|
// Обновление доступных родителей при изменении сообщества в форме
|
|
|
|
|
createEffect(on(() => formData().community, (communityId) => {
|
|
|
|
|
if (communityId > 0) {
|
|
|
|
|
updateAvailableParents(communityId)
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
// Обновление доступных родителей при смене сообщества
|
2025-07-03 09:15:10 +00:00
|
|
|
|
const updateAvailableParents = (communityId: number, excludeTopicId?: number) => {
|
2025-07-02 19:30:21 +00:00
|
|
|
|
const allTopics = topics()
|
2025-07-03 09:15:10 +00:00
|
|
|
|
const currentTopicId = excludeTopicId || formData().id
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
// Фильтруем топики того же сообщества, исключая текущий топик
|
|
|
|
|
const filteredTopics = allTopics.filter(
|
|
|
|
|
(topic) => topic.community === communityId && topic.id !== currentTopicId
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
setAvailableParents(filteredTopics)
|
|
|
|
|
}
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-03 09:15:10 +00:00
|
|
|
|
/**
|
|
|
|
|
* Преобразование Topic в TopicPill для компонента TopicPillsCloud
|
|
|
|
|
*/
|
|
|
|
|
const convertTopicsToTopicPills = (topics: Topic[]): TopicPill[] => {
|
|
|
|
|
return topics.map(topic => ({
|
|
|
|
|
id: topic.id.toString(),
|
|
|
|
|
title: topic.title || '',
|
|
|
|
|
slug: topic.slug || '',
|
|
|
|
|
community: getCommunityName(topic.community),
|
|
|
|
|
parent_ids: (topic.parent_ids || []).map(id => id.toString()),
|
2025-07-02 19:30:21 +00:00
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 09:15:10 +00:00
|
|
|
|
/**
|
|
|
|
|
* Обработка изменения выбора родительских топиков из таблеточек
|
|
|
|
|
*/
|
|
|
|
|
const handleParentSelectionChange = (selectedIds: string[]) => {
|
|
|
|
|
const parentIds = selectedIds.map(id => Number.parseInt(id))
|
2025-07-02 19:30:21 +00:00
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
...prev,
|
2025-07-03 09:15:10 +00:00
|
|
|
|
parent_ids: parentIds
|
2025-07-02 19:30:21 +00:00
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-03 09:15:10 +00:00
|
|
|
|
// Сообщество топика изменить нельзя, поэтому обработчик не нужен
|
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
// Обработка изменения полей формы
|
|
|
|
|
const handleFieldChange = (field: string, value: string) => {
|
|
|
|
|
setFormData((prev) => ({
|
|
|
|
|
...prev,
|
|
|
|
|
[field]: value
|
|
|
|
|
}))
|
2025-06-30 18:25:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
// Сохранение изменений
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
try {
|
|
|
|
|
setSaving(true)
|
|
|
|
|
|
2025-07-03 09:15:10 +00:00
|
|
|
|
const topicData = formData()
|
|
|
|
|
|
|
|
|
|
// Вызываем админскую мутацию для сохранения
|
|
|
|
|
const result = await query<{
|
|
|
|
|
adminUpdateTopic: {
|
|
|
|
|
success: boolean
|
|
|
|
|
error?: string
|
|
|
|
|
topic?: Topic
|
|
|
|
|
}
|
|
|
|
|
}>(`${location.origin}/graphql`, ADMIN_UPDATE_TOPIC_MUTATION, {
|
|
|
|
|
topic: {
|
|
|
|
|
id: topicData.id,
|
|
|
|
|
title: topicData.title,
|
|
|
|
|
slug: topicData.slug,
|
|
|
|
|
body: topicData.body,
|
|
|
|
|
community: topicData.community,
|
|
|
|
|
parent_ids: topicData.parent_ids
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-07-02 19:30:21 +00:00
|
|
|
|
|
2025-07-03 09:15:10 +00:00
|
|
|
|
if (result.adminUpdateTopic.success && result.adminUpdateTopic.topic) {
|
|
|
|
|
console.log('[TopicEditModal] Topic saved successfully:', result.adminUpdateTopic.topic)
|
|
|
|
|
props.onSave(result.adminUpdateTopic.topic)
|
|
|
|
|
props.onClose()
|
|
|
|
|
} else {
|
|
|
|
|
const errorMessage = result.adminUpdateTopic.error || 'Неизвестная ошибка'
|
|
|
|
|
throw new Error(errorMessage)
|
|
|
|
|
}
|
2025-07-02 19:30:21 +00:00
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('[TopicEditModal] Error saving topic:', error)
|
|
|
|
|
props.onError?.(error instanceof Error ? error.message : 'Ошибка сохранения топика')
|
|
|
|
|
} finally {
|
|
|
|
|
setSaving(false)
|
|
|
|
|
}
|
2025-06-30 18:25:26 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (
|
2025-07-02 19:30:21 +00:00
|
|
|
|
<>
|
|
|
|
|
<Modal
|
2025-07-03 09:15:10 +00:00
|
|
|
|
isOpen={props.isOpen}
|
2025-07-02 19:30:21 +00:00
|
|
|
|
onClose={props.onClose}
|
|
|
|
|
title="Редактирование топика"
|
|
|
|
|
size="large"
|
2025-07-03 09:15:10 +00:00
|
|
|
|
footer={
|
|
|
|
|
<>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class={`${styles.button} ${styles.secondary}`}
|
|
|
|
|
onClick={props.onClose}
|
|
|
|
|
disabled={saving()}
|
|
|
|
|
>
|
|
|
|
|
Отмена
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
class={`${styles.button} ${styles.primary}`}
|
|
|
|
|
onClick={handleSave}
|
|
|
|
|
disabled={saving() || !formData().title || !formData().slug || formData().community === 0}
|
|
|
|
|
>
|
|
|
|
|
{saving() ? 'Сохранение...' : 'Сохранить'}
|
|
|
|
|
</button>
|
|
|
|
|
</>
|
|
|
|
|
}
|
2025-07-02 19:30:21 +00:00
|
|
|
|
>
|
|
|
|
|
<div class={styles.form}>
|
|
|
|
|
{/* Основная информация */}
|
|
|
|
|
<div class={styles.section}>
|
|
|
|
|
<div class={styles.field}>
|
|
|
|
|
<label class={styles.label}>
|
|
|
|
|
Название:
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
class={styles.input}
|
|
|
|
|
value={formData().title}
|
|
|
|
|
onInput={(e) => handleFieldChange('title', e.currentTarget.value)}
|
|
|
|
|
placeholder="Введите название топика..."
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
<div class={styles.field}>
|
|
|
|
|
<label class={styles.label}>
|
|
|
|
|
Slug:
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
class={styles.input}
|
|
|
|
|
value={formData().slug}
|
|
|
|
|
onInput={(e) => handleFieldChange('slug', e.currentTarget.value)}
|
|
|
|
|
placeholder="Введите slug топика..."
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
<div class={styles.field}>
|
|
|
|
|
<label class={styles.label}>
|
|
|
|
|
Сообщество:
|
2025-07-03 09:15:10 +00:00
|
|
|
|
<div class={`${styles.input} ${styles.disabled} ${styles.communityDisplay}`}>
|
|
|
|
|
{getCommunityName(formData().community) || 'Сообщество не выбрано'}
|
|
|
|
|
</div>
|
2025-07-02 19:30:21 +00:00
|
|
|
|
</label>
|
2025-07-03 09:15:10 +00:00
|
|
|
|
<div class={`${styles.hint} ${styles.warningHint}`}>
|
|
|
|
|
📍 Сообщество топика нельзя изменить после создания
|
|
|
|
|
</div>
|
2025-07-02 19:30:21 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
{/* Содержимое */}
|
|
|
|
|
<div class={styles.section}>
|
|
|
|
|
<div class={styles.field}>
|
2025-07-03 09:15:10 +00:00
|
|
|
|
<label class={styles.label}>
|
|
|
|
|
Описание:
|
|
|
|
|
<HTMLEditor
|
|
|
|
|
value={formData().body}
|
|
|
|
|
onInput={(value) => handleFieldChange('body', value)}
|
|
|
|
|
/>
|
|
|
|
|
</label>
|
2025-07-02 19:30:21 +00:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-30 18:25:26 +00:00
|
|
|
|
|
2025-07-02 19:30:21 +00:00
|
|
|
|
{/* Родительские топики */}
|
|
|
|
|
<Show when={formData().community > 0}>
|
|
|
|
|
<div class={styles.section}>
|
2025-07-03 09:15:10 +00:00
|
|
|
|
{/* Компонент с таблеточками для выбора родителей */}
|
2025-07-02 19:30:21 +00:00
|
|
|
|
<div class={styles.field}>
|
2025-07-03 09:15:10 +00:00
|
|
|
|
<TopicPillsCloud
|
|
|
|
|
topics={convertTopicsToTopicPills(availableParents())}
|
|
|
|
|
selectedTopics={formData().parent_ids.map(id => id.toString())}
|
|
|
|
|
onSelectionChange={handleParentSelectionChange}
|
|
|
|
|
excludeTopics={[formData().id.toString()]}
|
|
|
|
|
showSearch={true}
|
|
|
|
|
searchPlaceholder="Задайте родительские темы..."
|
|
|
|
|
hideSelectedInHeader={true}
|
2025-07-02 19:30:21 +00:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Show>
|
|
|
|
|
|
2025-06-30 18:25:26 +00:00
|
|
|
|
</div>
|
2025-07-02 19:30:21 +00:00
|
|
|
|
</Modal>
|
|
|
|
|
</>
|
2025-06-30 18:25:26 +00:00
|
|
|
|
)
|
|
|
|
|
}
|