core/panel/modals/TopicEditModal.tsx

254 lines
8.6 KiB
TypeScript
Raw Normal View History

2025-07-18 13:32:35 +00:00
import { createEffect, createSignal, on, Show } 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-07-18 13:32:35 +00:00
import HTMLEditor from '../ui/HTMLEditor'
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'
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) {
2025-07-18 13:32:35 +00:00
const { topics, getCommunityName, selectedCommunity } = useData()
2025-07-02 19:30:21 +00:00
// Состояние формы
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
// Обновление доступных родителей при изменении сообщества в форме
2025-07-18 13:32:35 +00:00
createEffect(
on(
() => formData().community,
(communityId) => {
if (communityId > 0) {
updateAvailableParents(communityId)
}
}
)
)
2025-07-03 09:15:10 +00:00
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[] => {
2025-07-18 13:32:35 +00:00
return topics.map((topic) => ({
2025-07-03 09:15:10 +00:00
id: topic.id.toString(),
title: topic.title || '',
slug: topic.slug || '',
community: getCommunityName(topic.community),
2025-07-18 13:32:35 +00:00
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[]) => {
2025-07-18 13:32:35 +00:00
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}>
Описание:
2025-07-18 13:32:35 +00:00
<HTMLEditor value={formData().body} onInput={(value) => handleFieldChange('body', value)} />
2025-07-03 09:15:10 +00:00
</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
2025-07-18 13:32:35 +00:00
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
)
}