erp-platform/ui/src/views/public/Blog.tsx
2025-08-20 09:22:04 +03:00

276 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useState } from 'react'
import { Link } from 'react-router-dom'
import { FaCalendarAlt, FaUser, FaTag, FaSearch } from 'react-icons/fa'
import dayjs from 'dayjs'
import 'dayjs/locale/tr'
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
import { blogService } from '@/services/blog.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
const Blog = () => {
const { translate } = useLocalization()
const [posts, setPosts] = useState<BlogPost[]>([])
const [categories, setCategories] = useState<BlogCategory[]>([])
const [loading, setLoading] = useState(true)
const [selectedCategory, setSelectedCategory] = useState<string>('')
const [searchQuery, setSearchQuery] = useState('')
const [currentPage, setCurrentPage] = useState(1)
const [totalPages, setTotalPages] = useState(1)
dayjs.locale('tr')
useEffect(() => {
loadBlogData()
}, [currentPage, selectedCategory])
const loadBlogData = async () => {
try {
setLoading(true)
const postsData = await blogService.getPosts({
page: currentPage,
pageSize: 10,
categoryId: selectedCategory,
search: searchQuery,
})
if (
postsData.posts &&
postsData.posts.items &&
postsData.posts.totalCount &&
postsData.categories
) {
setPosts(postsData.posts.items.filter((a) => a.isPublished))
setTotalPages(postsData.posts.totalCount / 10)
setCategories(postsData.categories.filter((a) => a.isActive))
}
} catch (error) {
console.error('Blog verileri yüklenemedi:', error)
setPosts([])
} finally {
setLoading(false)
}
}
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
setCurrentPage(1)
loadBlogData()
}
const handleCategoryChange = (categoryId: string) => {
setSelectedCategory(categoryId)
setCurrentPage(1)
}
if (loading && posts.length === 0) {
return (
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
<div className="text-center">
<p className="mt-4 text-gray-600">Blog yazıları yükleniyor...</p>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gray-50">
<Helmet
titleTemplate="%s | Sözsoft"
title={translate('::' + 'Public.blog.title')}
defaultTitle="Sözsoft"
></Helmet>
{/* Hero Section */}
<div className="relative bg-blue-900 text-white py-12">
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage:
'url("https://images.pexels.com/photos/3183164/pexels-photo-3183164.jpeg?auto=compress&cs=tinysrgb&w=1920")',
backgroundSize: 'cover',
backgroundPosition: 'center',
}}
></div>
<div className="container mx-auto pt-20 relative">
<h1 className="text-5xl font-bold ml-4 mt-3 mb-2 text-white">
{translate('::Public.blog.title')}
</h1>
<p className="text-xl max-w-3xl ml-4">{translate('::Public.blog.subtitle')}</p>
</div>
</div>
{/* Search and Filter Section */}
<div className="bg-white shadow-sm border-b">
<div className="container mx-auto px-4 py-6">
<div className="flex flex-col md:flex-row gap-4">
{/* Search */}
<form onSubmit={handleSearch} className="flex-1">
<div className="relative">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Blog yazılarında ara..."
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<FaSearch className="absolute left-3 top-2.5 h-5 w-5 text-gray-400" />
</div>
</form>
{/* Category Filter */}
<div className="flex gap-2 flex-wrap">
<button
onClick={() => handleCategoryChange('')}
className={`px-4 py-2 bg-blue-100 text-blue-800 text-sm font-medium rounded-lg transition-colors ${
selectedCategory === ''
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{translate('::App.Reports.Dashboard.All')}
</button>
{categories.map((category) => (
<button
key={category.id}
onClick={() => handleCategoryChange(category.id)}
className={`px-4 py-2 bg-blue-100 text-blue-800 text-sm font-medium rounded-lg transition-colors ${
selectedCategory === category.id
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
}`}
>
{translate('::Public.' + category.name)} ({category.postCount})
</button>
))}
</div>
</div>
</div>
</div>
{/* Blog Posts Grid */}
<div className="container mx-auto px-4 py-16">
{!Array.isArray(posts) || posts.length === 0 ? (
<div className="text-center py-12">
<p className="text-gray-600 text-lg">Henüz blog yazısı bulunmuyor.</p>
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{posts.map((post) => (
<Link to={`/blog/${post.slug || post.id}`} key={post.id} className="block">
<article className="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-xl transition-shadow h-full flex flex-col">
<div className="aspect-w-16 aspect-h-9 relative">
<img
src={post.coverImage}
alt={post.title}
className="object-cover w-full h-48"
/>
<div className="absolute top-4 right-4 bg-blue-600 text-white px-3 py-1 rounded-full text-sm">
{translate('::Public.' + post.category.name)}
</div>
</div>
<div className="p-6 flex-1 flex flex-col">
<h2 className="text-xl font-bold text-gray-900 mb-3 hover:text-blue-600 transition-colors">
{translate('::Public.' + post.title)}
</h2>
<p className="text-gray-600 mb-4 flex-1">
{translate('::Public.' + post.summary)}
</p>
{/* Tags */}
{post.tags.length > 0 && (
<div className="flex flex-wrap gap-2 mb-4">
{post.tags.slice(0, 3).map((tag, index) => (
<span
key={index}
className="inline-flex items-center text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded"
>
<FaTag className="w-3 h-3 mr-1" />
{tag}
</span>
))}
</div>
)}
<div className="flex items-center text-sm text-gray-500 space-x-4">
<div className="flex items-center">
<FaUser size={16} className="mr-1" />
{post.author.name}
</div>
<div className="flex items-center">
<FaCalendarAlt size={16} className="mr-1" />
{dayjs(post.publishedAt || post.creationTime).format('DD MMM YYYY')}
</div>
</div>
</div>
</article>
</Link>
))}
</div>
)}
{/* Pagination */}
{totalPages > 1 && (
<div className="mt-12 flex justify-center">
<nav className="flex gap-2">
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Önceki
</button>
{[...Array(totalPages)].map((_, i) => (
<button
key={i + 1}
onClick={() => setCurrentPage(i + 1)}
className={`px-4 py-2 rounded-lg ${
currentPage === i + 1
? 'bg-blue-600 text-white'
: 'border border-gray-300 hover:bg-gray-50'
}`}
>
{i + 1}
</button>
))}
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
>
Sonraki
</button>
</nav>
</div>
)}
</div>
{/* Newsletter Section */}
<div className="bg-white py-16">
<div className="container mx-auto px-4 text-center">
<h2 className="text-3xl font-bold text-gray-900 mb-4">
{translate('::Public.blog.subscribe')}
</h2>
<p className="text-gray-600 mb-8 max-w-2xl mx-auto">
{translate('::Public.blog.subscribe.desc')}
</p>
<div className="max-w-md mx-auto flex gap-4">
<input
type="email"
placeholder={translate('::Public.common.email')}
className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button className="bg-blue-600 text-white px-6 py-2 rounded-lg hover:bg-blue-700 transition-colors">
{translate('::Public.common.subscribe')}
</button>
</div>
</div>
</div>
</div>
)
}
export default Blog