announsement

This commit is contained in:
hamid zarghami
2025-10-22 14:45:51 +03:30
parent aa4bf7ecdb
commit 8ec11d2711
9 changed files with 811 additions and 3 deletions
+209
View File
@@ -0,0 +1,209 @@
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
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 formik = useFormik<AnnoncementCreateType>({
initialValues: {
title: '',
content: '',
publishAt: '',
isPublic: false,
isImportant: false,
serviceId: '',
groupIds: [],
userIds: undefined
},
validationSchema: Yup.object({
title: Yup.string().required(t('errors.required')),
content: Yup.string().required(t('errors.required')),
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])
// }
// })
}
})
// 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'>
<div>
{t('announcement.add_announcement')}
</div>
<div>
<Button
className='px-5'
onClick={() => formik.handleSubmit()}
// isLoading={createAnnoncement.isPending} // Commented out - API call
>
<div className='flex gap-2'>
<TickCircle
className='size-5'
color='black'
/>
<div>{t('announcement.submit_announcement')}</div>
</div>
</Button>
</div>
</div>
<div className='flex gap-6'>
<div className='flex-1'>
<div className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<Input
label={t('announcement.title')}
name='title'
onChange={formik.handleChange}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
<div className='mt-7 text-sm'>{t('announcement.text')}</div>
<div className='mt-1'>
<Textarea
placeholder={'توضیحات اعلان'}
name='content'
onChange={formik.handleChange}
error_text={formik.touched.content && formik.errors.content ? formik.errors.content : ''}
/>
</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'
className='size-4 min-w-4'
/>
<p className='text-[10px] text-description'>
{t('announcement.info_publishdata')}
</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')}
placeholder={t('announcement.select_service')}
items={[]} // Commented out - API data
// items={getServices?.data?.data?.services?.map((item: ServiceItemType) => ({
// label: item.name,
// value: item.id
// })) || []}
name='serviceId'
onChange={(e) => {
formik.setFieldValue('serviceId', e.target.value)
// setServiceId(e.target.value) // Commented out - unused
}}
/>
</div>
<div className='mt-5'>
<Select
placeholder={t('select')}
label={t('announcement.select_customer')}
items={[]} // Commented out - API data
// items={getCustomer?.data?.data?.users?.map((item: UserServiceItemType) => ({
// label: item.firstname + ' ' + item.lastname,
// value: item.id
// })) || []}
onChange={(e) => {
// setCustomerId(e.target.value) // Commented out - unused
console.log('Customer selected:', e.target.value)
}}
/>
</div>
</div>
</div>
</div>
)
}
export default Create
+207
View File
@@ -0,0 +1,207 @@
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 type { AnnoncementItemType } from './types/AnnoncementTypes'
import Button from '../../components/Button'
import { Add, Edit, Trash } from 'iconsax-react'
import Table from '../../components/Table'
import moment from 'moment-jalaali'
import { toast } from 'react-toastify'
// import { ErrorType } from '../../helpers/types'
import type { ColumnType } from '../../components/types/TableTypes'
const AnnouncementtList: FC = () => {
// const { t } = useTranslation('global')
const [page, setPage] = useState<number>(1)
// Mock translation function for static data
const t = (key: string) => {
const translations: { [key: string]: string } = {
'announcement.announcements': 'اعلانات',
'announcement.add_announcement': 'افزودن اعلان',
'announcement.title': 'عنوان',
'announcement.summary': 'خلاصه',
'announcement.service': 'سرویس',
'announcement.created_date': 'تاریخ ایجاد',
'announcement.publish_date': 'تاریخ انتشار',
'success': 'موفقیت آمیز'
}
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])
// }
// })
}
// Define columns for the Table component
const columns: ColumnType<AnnoncementItemType>[] = [
{
title: t('announcement.title'),
key: 'title',
render: (item) => item.title
},
{
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 || ''
},
{
title: t('announcement.created_date'),
key: 'createdAt',
render: (item) => (
<div className='dltr text-right'>
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
)
},
{
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' />
}
]
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'
>
<div className='flex gap-2'>
<Add
className='size-5'
color='black'
/>
<div>{t('announcement.add_announcement')}</div>
</div>
</Button>
</Link>
</div>
</div>
<Table
columns={columns}
data={staticData.data.announcements}
rowActions={rowActions}
pagination={{
currentPage: page,
totalPages: staticData.data.pager.totalPages,
onPageChange: setPage
}}
/>
</div>
)
}
export default AnnouncementtList
+231
View File
@@ -0,0 +1,231 @@
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 { 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
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 formik = useFormik<AnnoncementCreateType>({
initialValues: {
title: '',
content: '',
publishAt: '',
isPublic: false,
isImportant: false,
serviceId: '',
groupIds: [],
userIds: undefined
},
validationSchema: Yup.object({
title: Yup.string().required(t('errors.required')),
content: Yup.string().required(t('errors.required')),
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])
// }
// })
}
})
// 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])
return (
<div className='w-full mt-4'>
<div className='flex w-full justify-between items-center'>
<div>
ویرایش اطلاعیه
</div>
<div>
<Button
className='px-5'
onClick={() => formik.handleSubmit()}
// isLoading={updateAnnoncement.isPending} // Commented out - API call
>
<div className='flex gap-2'>
<TickCircle
className='size-5'
color='black'
/>
<div>{t('announcement.submit_announcement')}</div>
</div>
</Button>
</div>
</div>
<div className='flex gap-6'>
<div className='flex-1'>
<div className='flex-1 mt-10 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
<Input
label={t('announcement.title')}
name='title'
onChange={formik.handleChange}
value={formik.values.title}
error_text={formik.touched.title && formik.errors.title ? formik.errors.title : ''}
/>
<div className='mt-7 text-sm'>{t('announcement.text')}</div>
<div className='mt-1'>
<textarea
value={formik.values.content}
onChange={(e) => formik.setFieldValue('content', e.target.value)}
className="w-full p-3 border border-gray-300 rounded-lg"
style={{ minHeight: '120px' }}
placeholder="Enter announcement content..."
/>
</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')}
defaultValue={formik.values.publishAt}
/>
<div className='flex gap-1 mt-3'>
<InfoCircle
color='#8C90A3'
className='size-4 min-w-4'
/>
<p className='text-[10px] text-description'>
{t('announcement.info_publishdata')}
</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')}
placeholder={t('announcement.select_service')}
items={[]} // Commented out - API data
// items={getServices?.data?.data?.services?.map((item: ServiceItemType) => ({
// label: item.name,
// value: item.id
// })) || []}
name='serviceId'
onChange={(e) => {
formik.setFieldValue('serviceId', e.target.value)
// setServiceId(e.target.value) // Commented out - unused
}}
/>
</div>
<div className='mt-5'>
<Select
placeholder={t('select')}
label={t('announcement.select_customer')}
items={[]} // Commented out - API data
// items={getCustomer?.data?.data?.users?.map((item: UserServiceItemType) => ({
// label: item.firstname + ' ' + item.lastname,
// value: item.id
// })) || []}
onChange={(e) => {
// setCustomerId(e.target.value) // Commented out - unused
console.log('Customer selected:', e.target.value)
}}
/>
</div>
</div>
</div>
</div>
)
}
export default Update
@@ -0,0 +1,50 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import { type AnnoncementCreateType } from "../types/AnnoncementTypes";
import * as api from "../service/AnnoncementService";
export const useCreateAnnoncement = () => {
return useMutation({
mutationFn: (variables: AnnoncementCreateType) =>
api.createAnnoncement(variables),
});
};
export const useGetAnnoncements = (
page: number,
search: string,
since: string
) => {
return useQuery({
queryKey: ["announcements", page, search, since],
queryFn: () => api.getAnnoncements(page, search, since),
});
};
export const useGetAnnoncementDetail = (id: string) => {
return useQuery({
queryKey: ["announcement", id],
queryFn: () => api.getAnnoncementDetail(id),
enabled: !!id,
});
};
export const useGetCustomersService = (serviceId: string) => {
return useQuery({
queryKey: ["customer-service", serviceId],
queryFn: () => api.getCustomerService(serviceId),
enabled: !!serviceId,
});
};
export const useUpdateAnnoncement = (id: string) => {
return useMutation({
mutationFn: (variables: AnnoncementCreateType) =>
api.updateAnnoncement(id, variables),
});
};
export const useDeleteAnnoncement = () => {
return useMutation({
mutationFn: (id: string) => api.deleteAnnoncement(id),
});
};
@@ -0,0 +1,45 @@
import moment from "moment-jalaali";
import axios from "../../../config/axios";
import {type AnnoncementCreateType } from "../types/AnnoncementTypes";
export const createAnnoncement = async (params: AnnoncementCreateType) => {
const { data } = await axios.post(`/announcements`, params);
return data;
};
export const updateAnnoncement = async (
id: string,
params: AnnoncementCreateType
) => {
const { data } = await axios.patch(`/announcements/${id}`, params);
return data;
};
export const getCustomerService = async (serviceId: string) => {
const { data } = await axios.get(`/danak-services/${serviceId}/users`);
return data;
};
export const getAnnoncements = async (
page: number,
search: string,
since: string
) => {
let query = `?page=${page}`;
if (search) query += `&q=${search}`;
if (since)
query += `&since=${moment(since, "jYYYY-jMM-jDD").format("YYYY-MM-DD")}`;
const { data } = await axios.get(`/announcements${query}`);
return data;
};
export const getAnnoncementDetail = async (id: string) => {
const { data } = await axios.get(`/announcements/${id}/admin`);
return data;
};
export const deleteAnnoncement = async (id: string) => {
const { data } = await axios.delete(`/announcements/${id}`);
return data;
};
@@ -0,0 +1,33 @@
export type AnnoncementCreateType = {
title: string;
content: string;
publishAt: string;
isPublic: boolean;
isImportant: boolean;
serviceId?: string;
groupIds: string[];
userIds?: string[];
};
export type AnnoncementItemType = {
id: string;
title: string;
content: string;
publishAt: string;
isPublic: boolean;
isImportant: boolean;
createdAt: string;
updatedAt: string;
targetService: {
id: string;
name: string;
};
};
export type UserServiceItemType = {
id: string;
email: string;
firstname: string;
lastname: string;
profilepic: string;
};