881 lines
30 KiB
TypeScript
881 lines
30 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
||
import Card from '@/components/ui/Card'
|
||
import Button from '@/components/ui/Button'
|
||
import Table from '@/components/ui/Table'
|
||
import Tag from '@/components/ui/Tag'
|
||
import Dialog from '@/components/ui/Dialog'
|
||
import { FormContainer, FormItem } from '@/components/ui/Form'
|
||
import Input from '@/components/ui/Input'
|
||
import Select from '@/components/ui/Select'
|
||
import Switcher from '@/components/ui/Switcher'
|
||
import { HiPlus, HiPencil, HiTrash, HiEye } from 'react-icons/hi'
|
||
import { useNavigate } from 'react-router-dom'
|
||
import {
|
||
blogService,
|
||
} from '@/services/blog.service'
|
||
import { format } from 'date-fns'
|
||
import { tr } from 'date-fns/locale'
|
||
import { Field, FieldProps, Form, Formik } from 'formik'
|
||
import * as Yup from 'yup'
|
||
import toast from '@/components/ui/toast'
|
||
import Notification from '@/components/ui/Notification'
|
||
import ReactQuill from 'react-quill'
|
||
import 'react-quill/dist/quill.snow.css'
|
||
import Tr from '@/components/ui/Table/Tr'
|
||
import Th from '@/components/ui/Table/Th'
|
||
import THead from '@/components/ui/Table/THead'
|
||
import TBody from '@/components/ui/Table/TBody'
|
||
import Td from '@/components/ui/Table/Td'
|
||
import { SelectBoxOption } from '@/shared/types'
|
||
import { Checkbox, Tabs } from '@/components/ui'
|
||
import { Helmet } from 'react-helmet'
|
||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||
import { ConfirmDialog } from '@/components/shared'
|
||
import { useStoreState } from '@/store/store'
|
||
import TabList from '@/components/ui/Tabs/TabList'
|
||
import TabNav from '@/components/ui/Tabs/TabNav'
|
||
import TabContent from '@/components/ui/Tabs/TabContent'
|
||
import { BlogCategory, BlogPost, CreateUpdateBlogCategoryDto, CreateUpdateBlogPostDto } from '@/proxy/blog/blog'
|
||
|
||
const validationSchema = Yup.object().shape({
|
||
title: Yup.string().required(),
|
||
summary: Yup.string().required(),
|
||
categoryId: Yup.string().required(),
|
||
content: Yup.string(),
|
||
tags: Yup.string(),
|
||
coverImage: Yup.string(),
|
||
isPublished: Yup.bool(),
|
||
})
|
||
|
||
const categoryValidationSchema = Yup.object().shape({
|
||
name: Yup.string().required(),
|
||
slug: Yup.string().required(),
|
||
description: Yup.string(),
|
||
icon: Yup.string(),
|
||
displayOrder: Yup.number(),
|
||
isActive: Yup.bool(),
|
||
})
|
||
|
||
const BlogManagement = () => {
|
||
const { translate } = useLocalization()
|
||
const navigate = useNavigate()
|
||
const [activeTab, setActiveTab] = useState<'posts' | 'categories'>('posts')
|
||
const [posts, setPosts] = useState<BlogPost[]>([])
|
||
const [categories, setCategories] = useState<BlogCategory[]>([])
|
||
const [loading, setLoading] = useState(false)
|
||
const [modalVisible, setModalVisible] = useState(false)
|
||
const [categoryModalVisible, setCategoryModalVisible] = useState(false)
|
||
const [editingPost, setEditingPost] = useState<BlogPost | null>(null)
|
||
const [editingCategory, setEditingCategory] = useState<BlogCategory | null>(null)
|
||
const { texts } = useStoreState((state) => state.abpConfig)
|
||
const categoryItems = categories?.map((cat) => ({
|
||
value: cat.id,
|
||
label: texts?.Platform[cat.name] + ' (' + cat.name + ')',
|
||
}))
|
||
|
||
useEffect(() => {
|
||
loadData()
|
||
}, [])
|
||
|
||
const loadData = async () => {
|
||
setLoading(true)
|
||
try {
|
||
const [postsData, categoriesData] = await Promise.all([
|
||
blogService.getPosts({ pageSize: 100 }),
|
||
blogService.getCategories(),
|
||
])
|
||
setCategories(categoriesData)
|
||
setPosts(postsData.items)
|
||
} catch (error) {
|
||
toast.push(
|
||
<Notification title="Hata" type="danger">
|
||
{translate('::Error:Loading')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleCreate = () => {
|
||
setEditingPost(null)
|
||
setModalVisible(true)
|
||
}
|
||
|
||
const handleEdit = (post: BlogPost) => {
|
||
setEditingPost(post)
|
||
setModalVisible(true)
|
||
}
|
||
|
||
const handleDelete = async (id: string) => {
|
||
try {
|
||
await blogService.deletePost(id)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::KayitSilindi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
loadData()
|
||
} catch (error) {
|
||
toast.push(
|
||
<Notification title="Hata" type="danger">
|
||
{translate('::Error:Deleting')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
}
|
||
}
|
||
|
||
const handleSubmit = async (values: any, { setSubmitting }: any) => {
|
||
try {
|
||
const data: CreateUpdateBlogPostDto = {
|
||
title: values.title,
|
||
slug: values.slug,
|
||
contentTr: values.contentTr,
|
||
contentEn: values.contentEn,
|
||
summary: values.summary,
|
||
categoryId: values.categoryId,
|
||
tags: values.tags ? values.tags.split(',').map((t: string) => t.trim()) : [],
|
||
coverImage: values.coverImage,
|
||
isPublished: values.isPublished,
|
||
}
|
||
|
||
if (editingPost) {
|
||
await blogService.updatePost(editingPost.id, data)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::KayitGuncellendi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
} else {
|
||
await blogService.createPost(data)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::KayitEklendi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
}
|
||
|
||
setModalVisible(false)
|
||
loadData()
|
||
} catch (error) {
|
||
toast.push(
|
||
<Notification title="Hata" type="danger">
|
||
{translate('::IslemBasarisiz')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
const handlePublish = async (post: BlogPost) => {
|
||
try {
|
||
if (post.isPublished) {
|
||
await blogService.unpublishPost(post.id)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::YayinKaldirildi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
} else {
|
||
await blogService.publishPost(post.id)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::Yayinlandi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
}
|
||
loadData()
|
||
} catch (error) {
|
||
toast.push(
|
||
<Notification title="Hata" type="danger">
|
||
{translate('::IslemBasarisiz')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
}
|
||
}
|
||
|
||
// Category functions
|
||
const handleCreateCategory = () => {
|
||
setEditingCategory(null)
|
||
setCategoryModalVisible(true)
|
||
}
|
||
|
||
const handleEditCategory = (category: BlogCategory) => {
|
||
setEditingCategory(category)
|
||
//console.log(category)
|
||
setCategoryModalVisible(true)
|
||
}
|
||
|
||
const handleDeleteCategory = async (id: string) => {
|
||
try {
|
||
await blogService.deleteCategory(id)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::KayitSilindi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
loadData()
|
||
} catch (error) {
|
||
toast.push(
|
||
<Notification title="Hata" type="danger">
|
||
{translate('::IslemBasarisiz')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
}
|
||
}
|
||
|
||
const handleSubmitCategory = async (values: any, { setSubmitting }: any) => {
|
||
try {
|
||
const data: CreateUpdateBlogCategoryDto = {
|
||
name: values.name,
|
||
slug: values.slug,
|
||
description: values.description,
|
||
icon: values.icon,
|
||
displayOrder: values.displayOrder,
|
||
isActive: values.isActive,
|
||
}
|
||
|
||
if (editingCategory) {
|
||
await blogService.updateCategory(editingCategory.id, data)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::KayitGuncellendi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
} else {
|
||
await blogService.createCategory(data)
|
||
toast.push(
|
||
<Notification title="Başarılı" type="success">
|
||
{translate('::KayitEklendi')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
}
|
||
|
||
setCategoryModalVisible(false)
|
||
loadData()
|
||
} catch (error) {
|
||
toast.push(
|
||
<Notification title="Hata" type="danger">
|
||
{translate('::IslemBasarisiz')}
|
||
</Notification>,
|
||
{
|
||
placement: 'top-center',
|
||
},
|
||
)
|
||
} finally {
|
||
setSubmitting(false)
|
||
}
|
||
}
|
||
|
||
const initialValues = editingPost
|
||
? {
|
||
title: editingPost.title,
|
||
slug: editingPost.slug,
|
||
summary: editingPost.summary,
|
||
contentTr: editingPost.contentTr,
|
||
contentEn: editingPost.contentEn,
|
||
categoryId: editingPost.category.id,
|
||
tags: editingPost.tags.join(', '),
|
||
coverImage: editingPost.coverImage || '',
|
||
isPublished: editingPost.isPublished,
|
||
}
|
||
: {
|
||
title: '',
|
||
slug: '',
|
||
summary: '',
|
||
content: '',
|
||
categoryId: '',
|
||
tags: '',
|
||
coverImage: '',
|
||
isPublished: false,
|
||
}
|
||
|
||
const initialCategoryValues = editingCategory
|
||
? {
|
||
name: editingCategory.name,
|
||
slug: editingCategory.slug,
|
||
description: editingCategory.description || '',
|
||
icon: editingCategory.icon,
|
||
displayOrder: editingCategory.displayOrder,
|
||
isActive: editingCategory.isActive,
|
||
}
|
||
: {
|
||
name: '',
|
||
slug: '',
|
||
description: '',
|
||
icon: '',
|
||
displayOrder: 0,
|
||
isActive: true,
|
||
}
|
||
|
||
const [confirmDeletePost, setConfirmDeletePost] = useState<BlogPost | null>(null)
|
||
const [confirmDeleteCategory, setConfirmDeleteCategory] = useState<BlogCategory | null>(null)
|
||
|
||
const askDeletePost = (post: BlogPost) => {
|
||
setConfirmDeletePost(post)
|
||
}
|
||
|
||
const askDeleteCategory = (category: BlogCategory) => {
|
||
setConfirmDeleteCategory(category)
|
||
}
|
||
|
||
return (
|
||
<>
|
||
<Helmet
|
||
titleTemplate="%s | Kurs Platform"
|
||
title={translate('::' + 'Blog Management')}
|
||
defaultTitle="Kurs Platform"
|
||
></Helmet>
|
||
<Card>
|
||
<div className="flex gap-2 border-b">
|
||
<button
|
||
className={`p-2 rounded-t-md transition ${
|
||
activeTab === 'posts'
|
||
? 'bg-blue-100 text-blue-600 font-bold'
|
||
: 'text-gray-600 hover:bg-gray-100 font-semibold'
|
||
}`}
|
||
onClick={() => setActiveTab('posts')}
|
||
>
|
||
{translate('::blog.posts.title')}
|
||
</button>
|
||
<button
|
||
className={`p-2 rounded-t-md transition ${
|
||
activeTab === 'categories'
|
||
? 'bg-blue-100 text-blue-600 font-bold'
|
||
: 'text-gray-600 hover:bg-gray-100 font-semibold'
|
||
}`}
|
||
onClick={() => setActiveTab('categories')}
|
||
>
|
||
{translate('::blog.posts.categories')}
|
||
</button>
|
||
</div>
|
||
|
||
{activeTab === 'posts' ? (
|
||
<Table compact>
|
||
<THead>
|
||
<Tr>
|
||
<Th>{translate('::blog.posts.post.title')}</Th>
|
||
<Th>{translate('::blog.posts.post.slug')}</Th>
|
||
<Th>{translate('::blog.posts.post.category')}</Th>
|
||
<Th>{translate('::blog.posts.post.author')}</Th>
|
||
<Th>{translate('::blog.posts.post.publishDate')}</Th>
|
||
<Th>{translate('::blog.posts.post.status')}</Th>
|
||
<Th>
|
||
{' '}
|
||
<Button variant="solid" size="xs" icon={<HiPlus />} onClick={handleCreate}>
|
||
{translate('::New')}
|
||
</Button>
|
||
</Th>
|
||
</Tr>
|
||
</THead>
|
||
<TBody>
|
||
{loading ? (
|
||
<Tr>
|
||
<Td colSpan={7} className="text-center">
|
||
{translate('::Loading')}
|
||
</Td>
|
||
</Tr>
|
||
) : (
|
||
posts.map((post) => (
|
||
<Tr key={post.id}>
|
||
<Td>{texts?.Platform[post.title]}</Td>
|
||
<Td>{post.slug}</Td>
|
||
<Td>{texts?.Platform[post.category?.name]}</Td>
|
||
<Td>{post.author?.name}</Td>
|
||
<Td>
|
||
{post.publishedAt
|
||
? format(new Date(post.publishedAt), 'dd MMM yyyy', { locale: tr })
|
||
: '-'}
|
||
</Td>
|
||
<Td>
|
||
<Switcher
|
||
className="switcher-sm"
|
||
checked={post.isPublished}
|
||
onChange={() => handlePublish(post)}
|
||
/>
|
||
</Td>
|
||
<Td>
|
||
<div className="flex gap-2">
|
||
<Button size="xs" icon={<HiPencil />} onClick={() => handleEdit(post)} />
|
||
<Button
|
||
size="xs"
|
||
variant="solid"
|
||
color="red-600"
|
||
icon={<HiTrash />}
|
||
onClick={() => askDeletePost(post)}
|
||
/>
|
||
</div>
|
||
</Td>
|
||
</Tr>
|
||
))
|
||
)}
|
||
</TBody>
|
||
</Table>
|
||
) : (
|
||
<Table compact>
|
||
<THead>
|
||
<Tr>
|
||
<Th>{translate('::blog.posts.categories.name')}</Th>
|
||
<Th>{translate('::blog.posts.categories.slug')}</Th>
|
||
<Th>{translate('::blog.posts.categories.description')}</Th>
|
||
<Th>{translate('::blog.posts.categories.count')}</Th>
|
||
<Th>{translate('::blog.posts.categories.order')}</Th>
|
||
<Th>{translate('::blog.posts.categories.status')}</Th>
|
||
<Th>
|
||
<Button
|
||
variant="solid"
|
||
size="xs"
|
||
icon={<HiPlus />}
|
||
onClick={handleCreateCategory}
|
||
>
|
||
{translate('::New')}
|
||
</Button>
|
||
</Th>
|
||
</Tr>
|
||
</THead>
|
||
<TBody>
|
||
{loading ? (
|
||
<Tr>
|
||
<Td colSpan={5} className="text-center">
|
||
{translate('::Loading')}
|
||
</Td>
|
||
</Tr>
|
||
) : (
|
||
categories.map((category) => (
|
||
<Tr key={category.id}>
|
||
<Td>{texts?.Platform[category.name]}</Td>
|
||
<Td>{category.slug}</Td>
|
||
<Td>{texts?.Platform[category.description!!]}</Td>
|
||
<Td>{category.postCount}</Td>
|
||
<Td>{category.displayOrder}</Td>
|
||
<Td>
|
||
<Tag
|
||
className={
|
||
category.isActive
|
||
? 'bg-green-100 text-green-800'
|
||
: 'bg-orange-100 text-orange-800'
|
||
}
|
||
>
|
||
{category.isActive ? 'Aktif' : 'Pasif'}
|
||
</Tag>
|
||
</Td>
|
||
<Td>
|
||
<div className="flex gap-2">
|
||
<Button
|
||
size="xs"
|
||
icon={<HiPencil />}
|
||
onClick={() => handleEditCategory(category)}
|
||
/>
|
||
<Button
|
||
size="xs"
|
||
variant="solid"
|
||
color="red-600"
|
||
icon={<HiTrash />}
|
||
onClick={() => askDeleteCategory(category)}
|
||
/>
|
||
</div>
|
||
</Td>
|
||
</Tr>
|
||
))
|
||
)}
|
||
</TBody>
|
||
</Table>
|
||
)}
|
||
</Card>
|
||
|
||
{/* Post Modal */}
|
||
<Dialog
|
||
isOpen={modalVisible}
|
||
onClose={() => setModalVisible(false)}
|
||
onRequestClose={() => setModalVisible(false)}
|
||
width={1000}
|
||
>
|
||
<h5 className="mb-4">
|
||
{editingPost ? translate('::blog.posts.edittitle') : translate('::blog.posts.newtitle')}
|
||
</h5>
|
||
|
||
<Formik
|
||
initialValues={initialValues}
|
||
validationSchema={validationSchema}
|
||
onSubmit={handleSubmit}
|
||
enableReinitialize={true}
|
||
>
|
||
{({ values, touched, errors, isSubmitting, setFieldValue }) => (
|
||
<Form>
|
||
<FormContainer>
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.post.title')}
|
||
invalid={errors.title && touched.title}
|
||
errorMessage={errors.title}
|
||
>
|
||
<Field name="title">
|
||
{({ field, form }: FieldProps<SelectBoxOption>) => {
|
||
const options = texts?.Platform
|
||
? Object.entries(texts.Platform).map(([key, value]) => ({
|
||
value: key,
|
||
label: value + ' (' + key + ')',
|
||
})).filter(a=> a.value.startsWith("blog"))
|
||
: []
|
||
|
||
return (
|
||
<Select
|
||
field={field}
|
||
form={form}
|
||
options={options}
|
||
isClearable={true}
|
||
value={options.find((opt) => opt.value === field.value)}
|
||
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
|
||
/>
|
||
)
|
||
}}
|
||
</Field>
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.post.slug')}
|
||
invalid={errors.title && touched.title}
|
||
errorMessage={errors.title}
|
||
>
|
||
<Field type="text" name="slug" placeholder="Slug" component={Input} />
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.post.summary')}
|
||
invalid={errors.summary && touched.summary}
|
||
errorMessage={errors.summary}
|
||
>
|
||
<Field name="summary">
|
||
{({ field, form }: FieldProps<SelectBoxOption>) => {
|
||
const options = texts?.Platform
|
||
? Object.entries(texts.Platform).map(([key, value]) => ({
|
||
value: key,
|
||
label: value + ' (' + key + ')',
|
||
})).filter(a=> a.value.startsWith("blog"))
|
||
: []
|
||
|
||
return (
|
||
<Select
|
||
field={field}
|
||
form={form}
|
||
options={options}
|
||
isClearable={true}
|
||
value={options.find((opt) => opt.value === field.value)}
|
||
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
|
||
/>
|
||
)
|
||
}}
|
||
</Field>
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.post.category')}
|
||
invalid={errors.categoryId && touched.categoryId}
|
||
errorMessage={errors.categoryId}
|
||
>
|
||
<Field name="categoryId">
|
||
{({ field, form }: FieldProps<SelectBoxOption>) => (
|
||
<Select
|
||
field={field}
|
||
form={form}
|
||
options={categoryItems.filter(a=> a.label.startsWith("blog"))}
|
||
isClearable={true}
|
||
value={categoryItems.filter((option) => option.value === values.categoryId)}
|
||
onChange={(option) => form.setFieldValue(field.name, option?.value)}
|
||
/>
|
||
)}
|
||
</Field>
|
||
</FormItem>
|
||
|
||
<FormItem label={translate('::blog.posts.post.tags')}>
|
||
<Field
|
||
type="text"
|
||
name="tags"
|
||
placeholder="react, javascript, web"
|
||
component={Input}
|
||
/>
|
||
</FormItem>
|
||
|
||
<FormItem label={translate('::blog.posts.post.image')}>
|
||
<Field type="text" name="coverImage" component={Input} />
|
||
</FormItem>
|
||
|
||
<Tabs defaultValue="tr" variant="pill">
|
||
<TabList className="flex-wrap border-b mb-4 bg-slate-50 rounded-t">
|
||
<TabNav value="tr">Türkçe</TabNav>
|
||
<TabNav value="en">English</TabNav>
|
||
</TabList>
|
||
|
||
<TabContent value="tr">
|
||
<FormItem
|
||
label={translate('::blog.posts.post.content')}
|
||
asterisk
|
||
invalid={!!errors.contentTr}
|
||
errorMessage={errors.contentTr}
|
||
>
|
||
<ReactQuill
|
||
theme="snow"
|
||
value={values.contentTr}
|
||
onChange={(val: string) => setFieldValue('contentTr', val)}
|
||
style={{ height: '300px', marginBottom: '50px' }}
|
||
/>
|
||
</FormItem>
|
||
</TabContent>
|
||
|
||
<TabContent value="en">
|
||
<FormItem
|
||
label={translate('::blog.posts.post.content')}
|
||
asterisk
|
||
invalid={!!errors.contentEn}
|
||
errorMessage={errors.contentEn}
|
||
>
|
||
<ReactQuill
|
||
theme="snow"
|
||
value={values.contentEn}
|
||
onChange={(val: string) => setFieldValue('contentEn', val)}
|
||
style={{ height: '300px', marginBottom: '50px' }}
|
||
/>
|
||
</FormItem>
|
||
</TabContent>
|
||
</Tabs>
|
||
|
||
<FormItem
|
||
label={translate('::blog.posts.post.status')}
|
||
invalid={errors.isPublished && touched.isPublished}
|
||
errorMessage={errors.isPublished}
|
||
>
|
||
<Field name="isPublished" component={Switcher} />
|
||
</FormItem>
|
||
|
||
<FormItem>
|
||
<div className="flex gap-2">
|
||
<Button variant="solid" type="submit" loading={isSubmitting}>
|
||
{editingPost
|
||
? translate('::blog.posts.post.update')
|
||
: translate('::blog.posts.post.create')}
|
||
</Button>
|
||
<Button variant="plain" onClick={() => setModalVisible(false)}>
|
||
{translate('::Cancel')}
|
||
</Button>
|
||
</div>
|
||
</FormItem>
|
||
</FormContainer>
|
||
</Form>
|
||
)}
|
||
</Formik>
|
||
</Dialog>
|
||
|
||
{/* Category Modal */}
|
||
<Dialog
|
||
isOpen={categoryModalVisible}
|
||
onClose={() => setCategoryModalVisible(false)}
|
||
onRequestClose={() => setCategoryModalVisible(false)}
|
||
width={600}
|
||
>
|
||
<h5 className="mb-4">
|
||
{editingCategory
|
||
? translate('::blog.posts.categories.edittitle')
|
||
: translate('::blog.posts.categories.newtitle')}
|
||
</h5>
|
||
|
||
<Formik
|
||
initialValues={initialCategoryValues}
|
||
validationSchema={categoryValidationSchema}
|
||
onSubmit={handleSubmitCategory}
|
||
enableReinitialize
|
||
>
|
||
{({ values, touched, errors, isSubmitting }) => (
|
||
<Form>
|
||
<FormContainer>
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.categories.name')}
|
||
invalid={errors.name && touched.name}
|
||
errorMessage={errors.name}
|
||
>
|
||
<Field name="name">
|
||
{({ field, form }: FieldProps<SelectBoxOption>) => {
|
||
const options = texts?.Platform
|
||
? Object.entries(texts.Platform).map(([key, value]) => ({
|
||
value: key,
|
||
label: value + ' (' + key + ')',
|
||
}))
|
||
: []
|
||
|
||
return (
|
||
<Select
|
||
field={field}
|
||
form={form}
|
||
options={options}
|
||
isClearable={true}
|
||
value={options.find((opt) => opt.value === field.value)}
|
||
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
|
||
/>
|
||
)
|
||
}}
|
||
</Field>
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.categories.slug')}
|
||
invalid={errors.slug && touched.slug}
|
||
errorMessage={errors.slug}
|
||
>
|
||
<Field type="text" name="slug" component={Input} />
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
asterisk
|
||
label={translate('::blog.posts.categories.description')}
|
||
invalid={errors.description && touched.description}
|
||
errorMessage={errors.description}
|
||
>
|
||
<Field name="description">
|
||
{({ field, form }: FieldProps<SelectBoxOption>) => {
|
||
const options = texts?.Platform
|
||
? Object.entries(texts.Platform).map(([key, value]) => ({
|
||
value: key,
|
||
label: value + ' (' + key + ')',
|
||
}))
|
||
: []
|
||
|
||
return (
|
||
<Select
|
||
field={field}
|
||
form={form}
|
||
options={options}
|
||
isClearable={true}
|
||
value={options.find((opt) => opt.value === field.value)}
|
||
onChange={(option) => form.setFieldValue(field.name, option?.value || '')}
|
||
/>
|
||
)
|
||
}}
|
||
</Field>
|
||
</FormItem>
|
||
|
||
<FormItem label={translate('::blog.posts.categories.icon')}>
|
||
<Field type="text" name="icon" component={Input} />
|
||
</FormItem>
|
||
|
||
<FormItem label={translate('::blog.posts.categories.order')}>
|
||
<Field type="number" name="displayOrder" placeholder="0" component={Input} />
|
||
</FormItem>
|
||
|
||
<FormItem
|
||
label={translate('::blog.posts.categories.status')}
|
||
invalid={errors.isActive && touched.isActive}
|
||
errorMessage={errors.isActive}
|
||
>
|
||
<Field name="isActive" component={Checkbox} />
|
||
</FormItem>
|
||
|
||
<FormItem>
|
||
<div className="flex gap-2">
|
||
<Button variant="solid" type="submit" loading={isSubmitting}>
|
||
{editingCategory
|
||
? translate('::blog.posts.categories.update')
|
||
: translate('::blog.posts.categories.create')}
|
||
</Button>
|
||
<Button variant="plain" onClick={() => setCategoryModalVisible(false)}>
|
||
{translate('::Cancel')}
|
||
</Button>
|
||
</div>
|
||
</FormItem>
|
||
</FormContainer>
|
||
</Form>
|
||
)}
|
||
</Formik>
|
||
</Dialog>
|
||
|
||
{/* Post Silme Onayı */}
|
||
<ConfirmDialog
|
||
isOpen={!!confirmDeletePost}
|
||
type="danger"
|
||
title={translate('::DeleteConfirmation')}
|
||
confirmText={translate('::Delete')}
|
||
cancelText={translate('::Cancel')}
|
||
confirmButtonColor="red-600"
|
||
onCancel={() => setConfirmDeletePost(null)}
|
||
onConfirm={async () => {
|
||
if (confirmDeletePost) {
|
||
await handleDelete(confirmDeletePost.id)
|
||
setConfirmDeletePost(null)
|
||
}
|
||
}}
|
||
>
|
||
<p>
|
||
<span className="font-semibold">{confirmDeletePost?.title}</span>{' '}
|
||
{translate('::DeleteConfirmation')}
|
||
</p>
|
||
</ConfirmDialog>
|
||
|
||
{/* Kategori Silme Onayı */}
|
||
<ConfirmDialog
|
||
isOpen={!!confirmDeleteCategory}
|
||
type="danger"
|
||
title={translate('::DeleteConfirmation')}
|
||
confirmText={translate('::Delete')}
|
||
cancelText={translate('::Cancel')}
|
||
confirmButtonColor="red-600"
|
||
onCancel={() => setConfirmDeleteCategory(null)}
|
||
onConfirm={async () => {
|
||
if (confirmDeleteCategory) {
|
||
await handleDeleteCategory(confirmDeleteCategory.id)
|
||
setConfirmDeleteCategory(null)
|
||
}
|
||
}}
|
||
>
|
||
<p>
|
||
<span className="font-semibold">{confirmDeleteCategory?.name}</span>{' '}
|
||
{translate('::DeleteConfirmation')}
|
||
</p>
|
||
</ConfirmDialog>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default BlogManagement
|