diff --git a/frontend/src/pages/blog/category/[category].astro b/frontend/src/pages/blog/category/[category].astro index f379bfd..69461c4 100644 --- a/frontend/src/pages/blog/category/[category].astro +++ b/frontend/src/pages/blog/category/[category].astro @@ -8,8 +8,10 @@ import SidebarCategories from '@components/blog/SidebarCategories.astro'; import TagCloud from '@components/blog/TagCloud.astro'; import { MONTHS } from '@lib/constants'; +// Конфигурация const POCKETBASE_URL = import.meta.env.POCKETBASE_URL || "http://localhost:8090"; +// Типы interface PostRecord { id: string; title: string; @@ -29,22 +31,7 @@ interface CategoryItem { count: number; } -// Получаем параметр категории из URL -const { category } = Astro.params; - -// Декодируем категорию из URL (например, "ugolovnoe-pravo" -> "Уголовное право") -function decodeCategory(slug: string | undefined): string { - if (!slug) return ""; - // Заменяем дефисы на пробелы и приводим к правильному регистру - return slug - .replace(/-/g, ' ') - .replace(/\b\w/g, (char) => char.toUpperCase()); -} - -const categoryName = decodeCategory(category); - -// Загружаем посты из PocketBase с фильтром по категории -let posts: Array<{ +interface Post { id: string; image: string; category: string; @@ -52,43 +39,134 @@ let posts: Array<{ excerpt: string; date: string; slug: string; -}> = []; - -try { - // Фильтруем по полю category - const filter = encodeURIComponent(`category="${categoryName}"`); - const response = await fetch(`${POCKETBASE_URL}/api/collections/posts/records?filter=${filter}&perPage=20&sort=-created`); - const data: PocketBaseResponse = await response.json(); - - if (data.items) { - posts = data.items.map((post) => { - const date = new Date(post.created); - const formattedDate = `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()} года`; - - // Формируем URL изображения - const imageUrl = post.image - ? `${POCKETBASE_URL}/api/files/posts/${post.id}/${post.image}` - : "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80"; - - // Берём категорию из поля category - const postCategory = post.category && post.category.trim() !== "" ? post.category : "НОВОСТИ"; - - return { - id: post.id, - image: imageUrl, - category: postCategory, - title: post.title, - excerpt: post.excerpt, - date: formattedDate, - slug: `/blog/${post.slug}` - }; - }); - } -} catch (error) { - console.error("Error fetching posts from PocketBase:", error); } -const popularPosts = [ +interface PopularPost { + image: string; + title: string; + views: string; +} + +// Получаем параметр из URL +const { category } = Astro.params; + +// Декодируем slug в название категории +function decodeCategory(slug: string | undefined): string { + if (!slug) return ""; + return slug + .replace(/-/g, ' ') + .replace(/\b\w/g, (char) => char.toUpperCase()); +} + +const categoryName = decodeCategory(category); + +// Форматирование даты +function formatDate(dateString: string): string { + const date = new Date(dateString); + return `${date.getDate()} ${MONTHS[date.getMonth()]} ${date.getFullYear()} года`; +} + +// Получение URL изображения +function getImageUrl(post: PostRecord): string { + if (post.image) { + return `${POCKETBASE_URL}/api/files/posts/${post.id}/${post.image}`; + } + return "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=80"; +} + +// Получение категории поста +function getPostCategory(post: PostRecord): string { + return post.category?.trim() || "НОВОСТИ"; +} + +// Загрузка постов по категории +async function fetchPostsByCategory(category: string): Promise { + if (!category) return []; + + try { + const filter = encodeURIComponent(`category="${category}"`); + const url = `${POCKETBASE_URL}/api/collections/posts/records?filter=${filter}&perPage=20&sort=-created`; + + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: PocketBaseResponse = await response.json(); + + if (!data.items) return []; + + return data.items.map((post) => ({ + id: post.id, + image: getImageUrl(post), + category: getPostCategory(post), + title: post.title, + excerpt: post.excerpt, + date: formatDate(post.created), + slug: `/blog/${post.slug}` + })); + } catch (error) { + console.error("[CategoryPage] Error fetching posts:", error); + return []; + } +} + +// Загрузка всех категорий +async function fetchCategories(): Promise { + try { + const response = await fetch(`${POCKETBASE_URL}/api/collections/posts/records?perPage=500`); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: PocketBaseResponse = await response.json(); + + if (!data.items) return []; + + const categoryMap = new Map(); + + data.items.forEach((post) => { + const cat = getPostCategory(post); + categoryMap.set(cat, (categoryMap.get(cat) || 0) + 1); + }); + + return Array.from(categoryMap.entries()) + .map(([name, count]) => ({ name, count })) + .sort((a, b) => b.count - a.count) + .slice(0, 6); + } catch (error) { + console.error("[CategoryPage] Error fetching categories:", error); + return []; + } +} + +// Склонение слова "статья" +function getArticlesCountText(count: number): string { + const lastDigit = count % 10; + const lastTwoDigits = count % 100; + + if (lastTwoDigits >= 11 && lastTwoDigits <= 19) { + return `${count} статей`; + } + + if (lastDigit === 1) { + return `${count} статья`; + } + + if (lastDigit >= 2 && lastDigit <= 4) { + return `${count} статьи`; + } + + return `${count} статей`; +} + +// Данные страницы +const posts = await fetchPostsByCategory(categoryName); +const categories = await fetchCategories(); + +const popularPosts: PopularPost[] = [ { image: "https://images.unsplash.com/photo-1556761175-5973dc0f32e7?w=200&h=200&fit=crop&q=80", title: "Как обжаловать решение суда первой инстанции", @@ -106,35 +184,11 @@ const popularPosts = [ } ]; -// Загружаем категории для сайдбара -let categories: CategoryItem[] = []; - -try { - const allPostsResponse = await fetch(`${POCKETBASE_URL}/api/collections/posts/records?perPage=500`); - const allPostsData: PocketBaseResponse = await allPostsResponse.json(); - - if (allPostsData.items) { - const categoryMap = new Map(); - - allPostsData.items.forEach((post) => { - const cat = post.category && post.category.trim() !== "" ? post.category : "НОВОСТИ"; - const currentCount = categoryMap.get(cat) || 0; - categoryMap.set(cat, currentCount + 1); - }); - - categories = Array.from(categoryMap.entries()) - .map(([name, count]) => ({ name, count })) - .sort((a, b) => b.count - a.count) - .slice(0, 6); - } -} catch (error) { - console.error("Error fetching categories from PocketBase:", error); - categories = []; -} - -// SEO данные -const pageTitle = `${categoryName} — Статьи и публикации`; -const pageDescription = `Все статьи категории "${categoryName}" на сайте адвоката`; +// SEO +const pageTitle = categoryName ? `${categoryName} — Статьи и публикации` : "Категории — Блог"; +const pageDescription = categoryName + ? `Все статьи категории "${categoryName}" на сайте адвоката` + : "Все категории статей на сайте адвоката"; --- @@ -27,21 +27,24 @@ import Toast from "@components/base/Toast.astro"; + \ No newline at end of file diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts new file mode 100644 index 0000000..35a968f --- /dev/null +++ b/frontend/src/types/global.d.ts @@ -0,0 +1,7 @@ +export {}; + +declare global { + interface Window { + showToast: (message: string, type?: 'success' | 'error' | 'info', duration?: number) => void; + } +} \ No newline at end of file