feat: фильтры ленты синхронизируются с URL (расшариваемые ссылки)

NewsFeed читает category/tag/period/q из query-параметров при загрузке
и пишет их обратно в URL при изменении фильтров. Теперь адрес в строке
браузера — готовая ссылка: ?category=slug1,slug2 открывает ленту сразу
с выбранными колледжами (для кнопок на внешнем сайте).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jze9
2026-07-12 22:13:32 +05:00
parent 3161470a7d
commit d4f0020c2c

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'
import { useSearchParams } from 'react-router-dom'
import { useTheme } from '../hooks/useTheme'
import { getNews, getCategories, getTags, getArticle } from '../api/index'
import NewsCard from '../components/NewsCard'
@@ -19,6 +20,7 @@ function presetToDateFrom(preset) {
export default function NewsFeed() {
const { dark, toggle } = useTheme()
const [searchParams, setSearchParams] = useSearchParams()
const [articles, setArticles] = useState([])
const [total, setTotal] = useState(0)
const [categories, setCategories] = useState([])
@@ -26,15 +28,20 @@ export default function NewsFeed() {
const [loading, setLoading] = useState(true)
const [loadingMore, setLoadingMore] = useState(false)
const [hasMore, setHasMore] = useState(false)
const [filters, setFilters] = useState({ category: [], tag: null, datePreset: '' })
// Начальное состояние фильтров читаем из URL — ссылка открывает ленту с этими колледжами
const [filters, setFilters] = useState(() => ({
category: (searchParams.get('category') || '').split(',').filter(Boolean),
tag: searchParams.get('tag') || null,
datePreset: searchParams.get('period') || '',
}))
const offsetRef = useRef(0)
const busyRef = useRef(false)
const sentinelRef = useRef(null)
// Поиск с debounce
const [searchInput, setSearchInput] = useState('')
const [searchQuery, setSearchQuery] = useState('')
// Поиск с debounce (тоже инициализируется из URL)
const [searchInput, setSearchInput] = useState(() => searchParams.get('q') || '')
const [searchQuery, setSearchQuery] = useState(() => searchParams.get('q') || '')
const debounceRef = useRef(null)
const handleSearch = e => {
@@ -68,6 +75,16 @@ export default function NewsFeed() {
return params
}, [filters, searchQuery])
// Синхронизируем фильтры/поиск в URL, чтобы адрес можно было скопировать и расшарить
useEffect(() => {
const p = {}
if (filters.category.length) p.category = filters.category.join(',')
if (filters.tag) p.tag = filters.tag
if (filters.datePreset) p.period = filters.datePreset
if (searchQuery) p.q = searchQuery
setSearchParams(p, { replace: true })
}, [filters, searchQuery, setSearchParams])
// Сброс и первая загрузка при смене фильтров/поиска
useEffect(() => {
let cancelled = false