erp-platform/ui/src/views/public/Blog.tsx

277 lines
10 KiB
TypeScript
Raw Normal View History

2025-08-14 07:10:56 +00:00
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'
2025-08-14 07:10:56 +00:00
import { BlogCategory, BlogPost } from '@/proxy/blog/blog'
import { blogService } from '@/services/blog.service'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Helmet } from 'react-helmet'
2025-05-15 10:48:03 +00:00
2025-06-19 14:51:10 +00:00
const Blog = () => {
2025-08-11 06:34:44 +00:00
const { translate } = useLocalization()
2025-08-14 07:10:56 +00:00
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)
2025-05-15 10:48:03 +00:00
dayjs.locale('tr')
2025-06-19 14:51:10 +00:00
useEffect(() => {
2025-08-14 07:10:56 +00:00
loadBlogData()
}, [currentPage, selectedCategory])
2025-06-19 14:51:10 +00:00
const loadBlogData = async () => {
try {
2025-08-14 07:10:56 +00:00
setLoading(true)
2025-06-19 14:51:10 +00:00
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))
}
2025-06-19 14:51:10 +00:00
} catch (error) {
2025-08-14 07:10:56 +00:00
console.error('Blog verileri yüklenemedi:', error)
setPosts([])
2025-06-19 14:51:10 +00:00
} finally {
2025-08-14 07:10:56 +00:00
setLoading(false)
2025-06-19 14:51:10 +00:00
}
2025-08-14 07:10:56 +00:00
}
2025-06-19 14:51:10 +00:00
const handleSearch = (e: React.FormEvent) => {
2025-08-14 07:10:56 +00:00
e.preventDefault()
setCurrentPage(1)
loadBlogData()
}
2025-05-15 10:48:03 +00:00
2025-06-19 14:51:10 +00:00
const handleCategoryChange = (categoryId: string) => {
2025-08-14 07:10:56 +00:00
setSelectedCategory(categoryId)
setCurrentPage(1)
}
2025-06-19 14:51:10 +00:00
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>
2025-08-14 07:10:56 +00:00
)
2025-06-19 14:51:10 +00:00
}
2025-05-15 10:48:03 +00:00
return (
<div className="min-h-screen bg-gray-50">
2025-08-14 07:10:56 +00:00
<Helmet
titleTemplate="%s | Sözsoft"
title={translate('::' + 'Public.blog.title')}
defaultTitle="Sözsoft"
></Helmet>
2025-05-15 10:48:03 +00:00
{/* Hero Section */}
2025-06-20 14:55:55 +00:00
<div className="relative bg-blue-900 text-white py-12">
2025-06-19 14:51:10 +00:00
<div
className="absolute inset-0 opacity-20"
style={{
backgroundImage:
2025-06-20 14:55:55 +00:00
'url("https://images.pexels.com/photos/3183164/pexels-photo-3183164.jpeg?auto=compress&cs=tinysrgb&w=1920")',
2025-08-14 07:10:56 +00:00
backgroundSize: 'cover',
backgroundPosition: 'center',
2025-06-19 14:51:10 +00:00
}}
></div>
2025-08-11 06:34:44 +00:00
<div className="container mx-auto pt-20 relative">
2025-08-14 07:10:56 +00:00
<h1 className="text-5xl font-bold ml-4 mt-3 mb-2 text-white">
{translate('::Public.blog.title')}
</h1>
2025-08-11 06:34:44 +00:00
<p className="text-xl max-w-3xl ml-4">{translate('::Public.blog.subtitle')}</p>
2025-06-19 14:51:10 +00:00
</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"
/>
2025-08-16 19:47:24 +00:00
<FaSearch className="absolute left-3 top-2.5 h-5 w-5 text-gray-400" />
2025-06-19 14:51:10 +00:00
</div>
</form>
{/* Category Filter */}
<div className="flex gap-2 flex-wrap">
<button
2025-08-14 07:10:56 +00:00
onClick={() => handleCategoryChange('')}
2025-08-17 19:07:04 +00:00
className={`px-4 py-2 bg-blue-100 text-blue-800 text-sm font-medium rounded-lg transition-colors ${
2025-08-14 07:10:56 +00:00
selectedCategory === ''
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
2025-06-19 14:51:10 +00:00
}`}
>
2025-08-17 19:07:04 +00:00
{translate('::App.Reports.Dashboard.All')}
2025-06-19 14:51:10 +00:00
</button>
{categories.map((category) => (
<button
key={category.id}
onClick={() => handleCategoryChange(category.id)}
2025-08-17 19:07:04 +00:00
className={`px-4 py-2 bg-blue-100 text-blue-800 text-sm font-medium rounded-lg transition-colors ${
2025-06-19 14:51:10 +00:00
selectedCategory === category.id
2025-08-14 07:10:56 +00:00
? 'bg-blue-600 text-white'
: 'bg-gray-200 text-gray-700 hover:bg-gray-300'
2025-06-19 14:51:10 +00:00
}`}
>
2025-08-14 07:10:56 +00:00
{translate('::Public.' + category.name)} ({category.postCount})
2025-06-19 14:51:10 +00:00
</button>
))}
</div>
</div>
2025-05-15 10:48:03 +00:00
</div>
</div>
{/* Blog Posts Grid */}
<div className="container mx-auto px-4 py-16">
2025-06-19 14:51:10 +00:00
{!Array.isArray(posts) || posts.length === 0 ? (
<div className="text-center py-12">
2025-08-14 07:10:56 +00:00
<p className="text-gray-600 text-lg">Henüz blog yazısı bulunmuyor.</p>
2025-06-19 14:51:10 +00:00
</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{posts.map((post) => (
2025-08-14 07:10:56 +00:00
<Link to={`/blog/${post.slug || post.id}`} key={post.id} className="block">
2025-06-19 14:51:10 +00:00
<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
2025-08-14 07:10:56 +00:00
src={post.coverImage}
2025-06-19 14:51:10 +00:00
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">
2025-08-11 06:34:44 +00:00
{translate('::Public.' + post.category.name)}
2025-06-19 14:51:10 +00:00
</div>
2025-05-15 10:48:03 +00:00
</div>
2025-06-19 14:51:10 +00:00
<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">
2025-08-11 06:34:44 +00:00
{translate('::Public.' + post.title)}
2025-06-19 14:51:10 +00:00
</h2>
2025-08-14 07:10:56 +00:00
<p className="text-gray-600 mb-4 flex-1">
{translate('::Public.' + post.summary)}
</p>
2025-06-19 14:51:10 +00:00
{/* 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"
>
2025-08-16 19:47:24 +00:00
<FaTag className="w-3 h-3 mr-1" />
2025-06-19 14:51:10 +00:00
{tag}
</span>
))}
</div>
)}
<div className="flex items-center text-sm text-gray-500 space-x-4">
2025-05-15 10:48:03 +00:00
<div className="flex items-center">
2025-08-16 19:47:24 +00:00
<FaUser size={16} className="mr-1" />
2025-06-19 14:51:10 +00:00
{post.author.name}
2025-05-15 10:48:03 +00:00
</div>
<div className="flex items-center">
2025-08-16 19:47:24 +00:00
<FaCalendarAlt size={16} className="mr-1" />
{dayjs(post.publishedAt || post.creationTime).format('DD MMM YYYY')}
2025-05-15 10:48:03 +00:00
</div>
</div>
</div>
</article>
</Link>
))}
2025-06-19 14:51:10 +00:00
</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
2025-08-14 07:10:56 +00:00
? 'bg-blue-600 text-white'
: 'border border-gray-300 hover:bg-gray-50'
2025-06-19 14:51:10 +00:00
}`}
>
{i + 1}
</button>
))}
<button
2025-08-14 07:10:56 +00:00
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
2025-06-19 14:51:10 +00:00
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>
)}
2025-05-15 10:48:03 +00:00
</div>
{/* Newsletter Section */}
<div className="bg-white py-16">
<div className="container mx-auto px-4 text-center">
2025-06-19 14:51:10 +00:00
<h2 className="text-3xl font-bold text-gray-900 mb-4">
2025-08-11 06:34:44 +00:00
{translate('::Public.blog.subscribe')}
2025-06-19 14:51:10 +00:00
</h2>
2025-05-15 10:48:03 +00:00
<p className="text-gray-600 mb-8 max-w-2xl mx-auto">
2025-08-11 06:34:44 +00:00
{translate('::Public.blog.subscribe.desc')}
2025-05-15 10:48:03 +00:00
</p>
<div className="max-w-md mx-auto flex gap-4">
<input
type="email"
2025-08-11 06:34:44 +00:00
placeholder={translate('::Public.common.email')}
2025-05-15 10:48:03 +00:00
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">
2025-08-11 06:34:44 +00:00
{translate('::Public.common.subscribe')}
2025-05-15 10:48:03 +00:00
</button>
</div>
</div>
</div>
</div>
2025-08-14 07:10:56 +00:00
)
}
2025-05-15 10:48:03 +00:00
2025-08-14 07:10:56 +00:00
export default Blog