myYouTube/frontend/src/pages/Categories.tsx

146 lines
4.6 KiB
TypeScript
Raw Normal View History

import { useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Link } from 'react-router-dom'
import {
createCategory,
deleteCategory,
listCategories,
reorderCategories,
updateCategory,
} from '../api/client'
function Categories() {
const queryClient = useQueryClient()
const [newName, setNewName] = useState('')
const [editingId, setEditingId] = useState<number | null>(null)
const [editingName, setEditingName] = useState('')
const categoriesQuery = useQuery({ queryKey: ['categories'], queryFn: listCategories })
const invalidate = () => {
queryClient.invalidateQueries({ queryKey: ['categories'] })
queryClient.invalidateQueries({ queryKey: ['channels'] })
}
const createMutation = useMutation({
mutationFn: (name: string) => createCategory(name),
onSuccess: () => {
setNewName('')
invalidate()
},
})
const updateMutation = useMutation({
mutationFn: ({ id, name }: { id: number; name: string }) => updateCategory(id, name),
onSuccess: () => {
setEditingId(null)
invalidate()
},
})
const deleteMutation = useMutation({
mutationFn: (id: number) => deleteCategory(id),
onSuccess: invalidate,
})
const reorderMutation = useMutation({
mutationFn: (ids: number[]) => reorderCategories(ids),
onSuccess: invalidate,
})
const categories = categoriesQuery.data ?? []
function move(index: number, direction: -1 | 1) {
const target = index + direction
if (target < 0 || target >= categories.length) return
const ids = categories.map((c) => c.id)
;[ids[index], ids[target]] = [ids[target], ids[index]]
reorderMutation.mutate(ids)
}
return (
<div className="categories-screen">
<Link to="/" className="back-link">
← Каналы
</Link>
<h1>Категории</h1>
<form
onSubmit={(e) => {
e.preventDefault()
if (newName.trim()) createMutation.mutate(newName.trim())
}}
className="category-form"
>
<input
type="text"
placeholder="Новая категория"
value={newName}
onChange={(e) => setNewName(e.target.value)}
/>
<button type="submit" disabled={!newName.trim() || createMutation.isPending}>
Создать
</button>
</form>
{createMutation.isError && <p className="error">{(createMutation.error as Error).message}</p>}
<ul className="category-list">
{categories.map((category, index) => (
<li key={category.id} className="category-row">
<div className="reorder-buttons">
<button onClick={() => move(index, -1)} disabled={index === 0} aria-label="Выше">
↑
</button>
<button onClick={() => move(index, 1)} disabled={index === categories.length - 1} aria-label="Ниже">
↓
</button>
</div>
{editingId === category.id ? (
<form
className="category-edit-form"
onSubmit={(e) => {
e.preventDefault()
if (editingName.trim()) updateMutation.mutate({ id: category.id, name: editingName.trim() })
}}
>
<input value={editingName} onChange={(e) => setEditingName(e.target.value)} autoFocus />
<button type="submit">Сохранить</button>
<button type="button" onClick={() => setEditingId(null)}>
Отмена
</button>
</form>
) : (
<>
<span className="category-name">{category.name}</span>
<span className="category-count">{category.channel_count} канал(ов)</span>
<button
onClick={() => {
setEditingId(category.id)
setEditingName(category.name)
}}
>
Переименовать
</button>
<button
onClick={() => {
if (confirm(`Удалить категорию "${category.name}"? Каналы останутся.`)) {
deleteMutation.mutate(category.id)
}
}}
>
Удалить
</button>
</>
)}
</li>
))}
</ul>
{categories.length === 0 && !categoriesQuery.isLoading && <p>Категорий пока нет.</p>}
</div>
)
}
export default Categories