announcement

This commit is contained in:
hamid zarghami
2026-02-22 15:17:56 +03:30
parent 51670fbc94
commit 6bdfd8129d
6 changed files with 146 additions and 294 deletions
+3
View File
@@ -66,4 +66,7 @@ export const fa = {
},
},
select: "انتخاب",
errors: {
required: "این فیلد الزامی است",
},
};
+47 -80
View File
@@ -1,36 +1,24 @@
import { type FC } from 'react'
// import { useTranslation } from 'react-i18next' // Commented out - missing dependency
import Button from '../../components/Button'
import { InfoCircle, TickCircle } from 'iconsax-react'
import Input from '../../components/Input'
// import Quill from 'quill' // Commented out - missing dependency
import DatePickerComponent from '../../components/DatePicker'
// import CheckBoxComponent from '../../components/CheckBoxComponent' // Commented out - component doesn't exist
import Select from '../../components/Select'
// import { useGetAllServices } from '../service/hooks/useServiceData' // Commented out - service doesn't exist
import { useFormik } from 'formik'
import { type AnnoncementCreateType } from './types/AnnoncementTypes'
import * as Yup from 'yup'
// import { ServiceItemType } from '../service/types/ServiceTypes' // Commented out - service doesn't exist
// import { useCreateAnnoncement, useGetCustomersService } from './hooks/useAnnoncementData' // Commented out - API calls
// import { ErrorType } from '../../helpers/types' // Commented out - API calls
// import { toast } from 'react-toastify' // Commented out - API calls
import moment from 'moment-jalaali'
import { t } from '@/locale'
import Textarea from '@/components/Textarea'
// import { useNavigate } from 'react-router-dom' // Commented out - unused
// import { Pages } from '../../config/Pages' // Commented out - unused
import { useNavigate } from 'react-router-dom'
import { Paths } from '@/config/Paths'
import { useCreateAnnoncement } from './hooks/useAnnoncementData'
import { toast } from '@/shared/toast'
import { extractErrorMessage } from '@/config/func'
const Create: FC = () => {
// const { t } = useTranslation('global') // Commented out - missing dependency
// const navigate = useNavigate() // Commented out - unused
// const editorRef = useRef<HTMLDivElement | null>(null); // Commented out - unused
// const [serviceId, setServiceId] = useState('') // Commented out - unused
// const [customerId, setCustomerId] = useState<string>('') // Commented out - unused
// const getServices = useGetAllServices('', 1, '', '', false) // Commented out - API call
// const createAnnoncement = useCreateAnnoncement() // Commented out - API call
// const getCustomer = useGetCustomersService(serviceId) // Commented out - API call
const navigate = useNavigate()
const createAnnoncement = useCreateAnnoncement()
const formik = useFormik<AnnoncementCreateType>({
initialValues: {
@@ -49,51 +37,22 @@ const Create: FC = () => {
publishAt: Yup.string().required(t('errors.required')),
}),
onSubmit: (values) => {
// API submission commented out
console.log('Form values:', values)
// if (!values.serviceId) {
// values.serviceId = undefined
// }
// if (customerId) {
// values.serviceId = undefined
// values.userIds = [customerId]
// }
// createAnnoncement.mutate(values, {
// onSuccess: () => {
// formik.resetForm()
// toast.success(t('success'))
// navigate(Pages.announcement.list)
// },
// onError: (error: ErrorType) => {
// toast.error(error.response?.data?.error?.message[0])
// }
// })
const payload = { ...values }
if (!payload.serviceId) payload.serviceId = undefined
createAnnoncement.mutate(payload, {
onSuccess: () => {
formik.resetForm()
toast(t('success'), 'success')
navigate(Paths.announcement.list)
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
}
})
}
})
// useEffect(() => {
// // Quill editor commented out - missing dependency
// const quill = new Quill('#editor', {
// theme: 'snow',
// modules: {
// toolbar: [
// [{ header: '1' }, { header: '2' }, { font: [] }],
// [{ list: 'ordered' }, { list: 'bullet' }],
// ['bold', 'italic', 'underline'],
// ['link', 'image'],
// [{ align: [] }],
// ['clean']
// ]
// }
// });
// quill.on('text-change', () => {
// const html = editorRef.current?.querySelector('.ql-editor')?.innerHTML || '';
// formik.setFieldValue('content', html);
// });
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
return (
<div className='w-full mt-4'>
<div className='flex w-full justify-between items-center'>
@@ -103,8 +62,11 @@ const Create: FC = () => {
<div>
<Button
className='px-5'
onClick={() => formik.handleSubmit()}
// isLoading={createAnnoncement.isPending} // Commented out - API call
onClick={() => {
formik.setTouched({ title: true, content: true, publishAt: true })
formik.handleSubmit()
}}
isLoading={createAnnoncement.isPending}
>
<div className='flex gap-2'>
<TickCircle
@@ -124,6 +86,7 @@ const Create: FC = () => {
label={t('announcement.title')}
name='title'
onChange={formik.handleChange}
value={formik.values.title}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
@@ -134,19 +97,35 @@ const Create: FC = () => {
placeholder={'توضیحات اعلان'}
name='content'
onChange={formik.handleChange}
value={formik.values.content}
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''}
/>
</div>
<div className='mt-6'>
<DatePickerComponent
label={t('announcement.publish_date')}
onChange={(d) => formik.setFieldValue('publishAt', moment(d, 'jYYYY-jMM-jDD').format('YYYY-MM-DD'))}
placeholder={t('select')}
/>
{formik.touched.publishAt && formik.errors.publishAt && (
<div className='text-xs text-red-600 mt-1'>{formik.errors.publishAt}</div>
)}
</div>
<div className='flex items-center mt-5'>
<input
type="checkbox"
checked={formik.values.isImportant}
onChange={(e) => formik.setFieldValue('isImportant', e.target.checked)}
className="mr-2"
/>
<div className='text-xs'>{t('announcement.label_importance')}</div>
</div>
</div>
</div>
<div className='bg-white mt-10 w-sidebar text-xs hidden 2xl:block h-fit px-5 py-7 rounded-3xl'>
<DatePickerComponent
label={t('announcement.publish_date')}
onChange={(d) => formik.setFieldValue('publishAt', moment(d, 'jYYYY-jMM-jDD').format('YYYY-MM-DD'))}
placeholder={t('select')}
/>
<div className='flex gap-1 mt-3'>
<InfoCircle
color='#8C90A3'
@@ -157,18 +136,6 @@ const Create: FC = () => {
</p>
</div>
<div className='flex items-center mt-5'>
<input
type="checkbox"
checked={formik.values.isImportant}
onChange={(e) => formik.setFieldValue('isImportant', e.target.checked)}
className="mr-2"
/>
<div className='text-xs'>{t('announcement.label_importance')}</div>
</div>
<div className='mt-5'>
<Select
label={t('announcement.contact_announcement')}
+48 -126
View File
@@ -1,24 +1,24 @@
import { useState } from 'react'
import type { FC } from 'react'
// import { useTranslation } from 'react-i18next'
import { Link } from 'react-router-dom'
// import { Pages } from '../../config/Pages'
// import { useDeleteAnnoncement, useGetAnnoncements } from './hooks/useAnnoncementData'
import { useDeleteAnnoncement, useGetAnnoncements } from './hooks/useAnnoncementData'
import type { AnnoncementItemType } from './types/AnnoncementTypes'
import Button from '../../components/Button'
import { Add, Edit, Trash } from 'iconsax-react'
import { Add, Edit } from 'iconsax-react'
import Table from '../../components/Table'
import moment from 'moment-jalaali'
import { toast } from 'react-toastify'
// import { ErrorType } from '../../helpers/types'
import { Paths } from '@/config/Paths'
import TrashWithConfrim from '@/components/TrashWithConfrim'
import { toast } from '@/shared/toast'
import { extractErrorMessage } from '@/config/func'
import type { ColumnType } from '../../components/types/TableTypes'
const AnnouncementtList: FC = () => {
// const { t } = useTranslation('global')
const AnnouncementList: FC = () => {
const [page, setPage] = useState<number>(1)
// Mock translation function for static data
const getAnnoncements = useGetAnnoncements(page, '', '')
const deleteAnnoncement = useDeleteAnnoncement()
const t = (key: string) => {
const translations: { [key: string]: string } = {
'announcement.announcements': 'اعلانات',
@@ -28,88 +28,30 @@ const AnnouncementtList: FC = () => {
'announcement.service': 'سرویس',
'announcement.created_date': 'تاریخ ایجاد',
'announcement.publish_date': 'تاریخ انتشار',
'success': 'موفقیت آمیز'
'success': 'موفقیت آمیز',
'detail': 'جزئیات'
}
return translations[key] || key
}
// Static data for testing - replace API calls
const staticData = {
data: {
announcements: [
{
id: '1',
title: 'اعلان اول',
content: '<p>این اولین اعلان تستی است</p>',
publishAt: '2024-01-15',
isPublic: true,
isImportant: false,
createdAt: '2024-01-10T10:00:00Z',
updatedAt: '2024-01-10T10:00:00Z',
targetService: {
id: '1',
name: 'سرویس اول'
}
},
{
id: '2',
title: 'اعلان مهم',
content: '<p>این اعلان مهم است</p>',
publishAt: '2024-01-20',
isPublic: true,
isImportant: true,
createdAt: '2024-01-15T14:30:00Z',
updatedAt: '2024-01-15T14:30:00Z',
targetService: {
id: '2',
name: 'سرویس دوم'
}
},
{
id: '3',
title: 'اعلان خصوصی',
content: '<p>این اعلان خصوصی است</p>',
publishAt: '2024-01-25',
isPublic: false,
isImportant: false,
createdAt: '2024-01-20T09:15:00Z',
updatedAt: '2024-01-20T09:15:00Z',
targetService: {
id: '3',
name: 'سرویس سوم'
}
}
],
pager: {
totalPages: 1,
currentPage: 1,
totalItems: 3
}
}
}
// Comment out API calls
// const getAnnoncements = useGetAnnoncements(page, '', '')
// const deleteAnnoncement = useDeleteAnnoncement()
const handleDelete = (id: string) => {
// Static delete handler - just show success message
console.log('Delete announcement with id:', id)
toast.success(t('success'))
// Comment out API delete call
// deleteAnnoncement.mutate(id, {
// onSuccess: () => {
// toast.success(t('success'))
// getAnnoncements.refetch()
// },
// onError: (error: ErrorType) => {
// toast.error(error.response?.data?.error.message[0])
// }
// })
deleteAnnoncement.mutate(id, {
onSuccess: () => {
toast(t('success'), 'success')
getAnnoncements.refetch()
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
}
})
}
const stripHtml = (html: string) => {
const tmp = document.createElement('div')
tmp.innerHTML = html
return tmp.textContent || tmp.innerText || ''
}
// Define columns for the Table component
const columns: ColumnType<AnnoncementItemType>[] = [
{
title: t('announcement.title'),
@@ -119,14 +61,10 @@ const AnnouncementtList: FC = () => {
{
title: t('announcement.summary'),
key: 'content',
render: (item) => (
<div dangerouslySetInnerHTML={{ __html: item.content }}></div>
)
},
{
title: t('announcement.service'),
key: 'targetService',
render: (item) => item?.targetService?.name || ''
render: (item) => {
const text = stripHtml(item.content)
return text.length > 100 ? `${text.substring(0, 100)}...` : text
}
},
{
title: t('announcement.created_date'),
@@ -141,43 +79,32 @@ const AnnouncementtList: FC = () => {
title: t('announcement.publish_date'),
key: 'publishAt',
render: (item) => moment(item.publishAt).format('jYYYY-jMM-jDD')
}
]
// Define row actions for the Table component
const rowActions = (item: AnnoncementItemType) => [
{
label: 'ویرایش',
onClick: () => {
// Navigate to edit page
window.location.href = `/announcement/detail/${item.id}`
},
icon: <Edit className='size-4' color='#888' />
},
{
label: 'حذف',
onClick: () => handleDelete(item.id),
icon: <Trash className='size-4' color='#888' />
title: '',
key: 'actions',
render: (item) => (
<div className='flex gap-2 items-center'>
<Link to={Paths.announcement.detail + item.id}>
<Edit className='size-4' color='#888' />
</Link>
<TrashWithConfrim onDelete={() => handleDelete(item.id)} />
</div>
)
}
]
return (
<div className='mt-4 text-sm'>
<div className='flex w-full justify-between items-center'>
<div>
{t('announcement.announcements')}
</div>
<div>
<Link to="/announcement/create">
<Button
className='px-5'
>
<Link to={Paths.announcement.create}>
<Button className='px-5'>
<div className='flex gap-2'>
<Add
className='size-5'
color='black'
/>
<Add className='size-5' color='black' />
<div>{t('announcement.add_announcement')}</div>
</div>
</Button>
@@ -185,23 +112,18 @@ const AnnouncementtList: FC = () => {
</div>
</div>
<Table
columns={columns}
data={staticData.data.announcements}
rowActions={rowActions}
data={getAnnoncements.data?.data?.announcements ?? []}
isLoading={getAnnoncements.isPending}
pagination={{
currentPage: page,
totalPages: staticData.data.pager.totalPages,
totalPages: getAnnoncements.data?.data?.pager?.totalPages ?? 1,
onPageChange: setPage
}}
/>
</div>
)
}
export default AnnouncementtList
export default AnnouncementList
+40 -82
View File
@@ -1,38 +1,25 @@
import { type FC } from 'react'
// import { useTranslation } from 'react-i18next' // Commented out - missing dependency
import { type FC, useEffect } from 'react'
import Button from '../../components/Button'
import { InfoCircle, TickCircle } from 'iconsax-react'
import Input from '../../components/Input'
// import Quill from 'quill' // Commented out - missing dependency
import DatePickerComponent from '../../components/DatePicker'
// import CheckBoxComponent from '../../components/CheckBoxComponent' // Commented out - component doesn't exist
import Select from '../../components/Select'
// import { useGetAllServices } from '../service/hooks/useServiceData' // Commented out - service doesn't exist
import { useFormik } from 'formik'
import { type AnnoncementCreateType } from './types/AnnoncementTypes'
import * as Yup from 'yup'
// import { ServiceItemType } from '../service/types/ServiceTypes' // Commented out - service doesn't exist
// import { useGetAnnoncementDetail, useGetCustomersService, useUpdateAnnoncement } from './hooks/useAnnoncementData' // Commented out - API calls
// import { ErrorType } from '../../helpers/types' // Commented out - API calls
// import { toast } from 'react-toastify' // Commented out - API calls
import moment from 'moment-jalaali'
import { t } from '@/locale'
// import { useParams } from 'react-router-dom' // Commented out - unused
// import { useNavigate } from 'react-router-dom' // Commented out - unused
// import { Pages } from '../../config/Pages' // Commented out - unused
import { useParams, useNavigate } from 'react-router-dom'
import { Paths } from '@/config/Paths'
import { useGetAnnoncementDetail, useUpdateAnnoncement } from './hooks/useAnnoncementData'
import { toast } from '@/shared/toast'
import { extractErrorMessage } from '@/config/func'
const Update: FC = () => {
// const { t } = useTranslation('global') // Commented out - missing dependency
// const navigate = useNavigate() // Commented out - unused
// const { id } = useParams() // Commented out - unused
// const editorRef = useRef<HTMLDivElement | null>(null); // Commented out - unused
// const [serviceId, setServiceId] = useState('') // Commented out - unused
// const [customerId, setCustomerId] = useState<string>('') // Commented out - unused
// const getServices = useGetAllServices('', 1, '', '', false) // Commented out - API call
// const getCustomer = useGetCustomersService(serviceId) // Commented out - API call
// const getAnnoncement = useGetAnnoncementDetail(id || '') // Commented out - API call
// const updateAnnoncement = useUpdateAnnoncement(id || '') // Commented out - API call
const navigate = useNavigate()
const { id } = useParams<{ id: string }>()
const getAnnoncement = useGetAnnoncementDetail(id || '')
const updateAnnoncement = useUpdateAnnoncement(id || '')
const formik = useFormik<AnnoncementCreateType>({
initialValues: {
@@ -51,67 +38,37 @@ const Update: FC = () => {
publishAt: Yup.string().required(t('errors.required')),
}),
onSubmit: (values) => {
// API submission commented out
console.log('Form values:', values)
// if (!values.serviceId) {
// values.serviceId = undefined
// }
// if (customerId) {
// values.serviceId = undefined
// values.userIds = [customerId]
// }
// updateAnnoncement.mutate(values, {
// onSuccess: () => {
// formik.resetForm()
// toast.success(t('success'))
// navigate(Pages.announcement.list)
// },
// onError: (error: ErrorType) => {
// toast.error(error.response?.data?.error?.message[0])
// }
// })
const payload = { ...values }
if (!payload.serviceId) payload.serviceId = undefined
updateAnnoncement.mutate(payload, {
onSuccess: () => {
formik.resetForm()
toast(t('success'), 'success')
navigate(Paths.announcement.list)
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
}
})
}
})
// useEffect(() => {
// // Quill editor commented out - missing dependency
// const quill = new Quill('#editor', {
// theme: 'snow',
// modules: {
// toolbar: [
// [{ header: '1' }, { header: '2' }, { font: [] }],
// [{ list: 'ordered' }, { list: 'bullet' }],
// ['bold', 'italic', 'underline'],
// ['link', 'image'],
// [{ align: [] }],
// ['clean']
// ]
// }
// });
// quill.on('text-change', () => {
// const html = editorRef.current?.querySelector('.ql-editor')?.innerHTML || '';
// formik.setFieldValue('content', html);
// });
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
// useEffect(() => {
// // Data loading commented out - API call
// if (getAnnoncement.data) {
// formik.setValues(getAnnoncement.data?.data?.announcement)
// setServiceId(getAnnoncement.data?.data?.announcement?.targetService?.id)
// setCustomerId(getAnnoncement.data?.data?.announcement?.userIds?.[0])
// const editorElement = editorRef.current?.querySelector('.ql-editor');
// if (editorElement) {
// editorElement.innerHTML = getAnnoncement.data?.data?.announcement?.content || '';
// }
// }
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, [getAnnoncement.data])
useEffect(() => {
const announcement = getAnnoncement.data?.data?.announcement
if (announcement) {
formik.setValues({
title: announcement.title ?? '',
content: announcement.content ?? '',
publishAt: announcement.publishAt ?? '',
isPublic: announcement.isPublic ?? false,
isImportant: announcement.isImportant ?? false,
serviceId: announcement.targetService?.id ?? '',
groupIds: announcement.groupIds ?? [],
userIds: announcement.userIds
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [getAnnoncement.data])
return (
<div className='w-full mt-4'>
@@ -123,7 +80,7 @@ const Update: FC = () => {
<Button
className='px-5'
onClick={() => formik.handleSubmit()}
// isLoading={updateAnnoncement.isPending} // Commented out - API call
isLoading={updateAnnoncement.isPending}
>
<div className='flex gap-2'>
<TickCircle
@@ -145,6 +102,7 @@ const Update: FC = () => {
onChange={formik.handleChange}
value={formik.values.title}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
disabled={getAnnoncement.isPending}
/>
<div className='mt-7 text-sm'>{t('announcement.text')}</div>
@@ -3,7 +3,7 @@ import axios from "../../../config/axios";
import {type AnnoncementCreateType } from "../types/AnnoncementTypes";
export const createAnnoncement = async (params: AnnoncementCreateType) => {
const { data } = await axios.post(`/announcements`, params);
const { data } = await axios.post(`/admin/announcements`, params);
return data;
};
@@ -11,7 +11,7 @@ export const updateAnnoncement = async (
id: string,
params: AnnoncementCreateType
) => {
const { data } = await axios.patch(`/announcements/${id}`, params);
const { data } = await axios.patch(`/admin/announcements/${id}`, params);
return data;
};
@@ -30,16 +30,16 @@ export const getAnnoncements = async (
if (since)
query += `&since=${moment(since, "jYYYY-jMM-jDD").format("YYYY-MM-DD")}`;
const { data } = await axios.get(`/announcements${query}`);
const { data } = await axios.get(`/admin/announcements${query}`);
return data;
};
export const getAnnoncementDetail = async (id: string) => {
const { data } = await axios.get(`/announcements/${id}/admin`);
const { data } = await axios.get(`/admin/announcements/${id}`);
return data;
};
export const deleteAnnoncement = async (id: string) => {
const { data } = await axios.delete(`/announcements/${id}`);
const { data } = await axios.delete(`/admin/announcements/${id}`);
return data;
};
@@ -18,10 +18,12 @@ export type AnnoncementItemType = {
isImportant: boolean;
createdAt: string;
updatedAt: string;
targetService: {
targetService?: {
id: string;
name: string;
};
groupIds?: string[];
userIds?: string[];
};
export type UserServiceItemType = {