ticket and annoncement

This commit is contained in:
hamid zarghami
2025-02-20 13:13:50 +03:30
parent 9f2762ff24
commit e094736c0e
11 changed files with 391 additions and 328 deletions
+4 -2
View File
@@ -168,7 +168,8 @@
"HIGH": "بالا", "HIGH": "بالا",
"PENDING": "در انتظار", "PENDING": "در انتظار",
"ANSWERED": "پاسخ داده شده", "ANSWERED": "پاسخ داده شده",
"CLOSED": "بسته شده" "CLOSED": "بسته شده",
"service": "سرویس"
}, },
"date": "تاریخ", "date": "تاریخ",
"receip": { "receip": {
@@ -373,7 +374,8 @@
"enter_address": " آدرس خود را با دقت وارد کنید.", "enter_address": " آدرس خود را با دقت وارد کنید.",
"state": "استان", "state": "استان",
"city": "شهر", "city": "شهر",
"notice_cannot_edit": "ویرایش مشخصات فردی بعد از تکمیل اطلاعات امکان پذیر نیست" "notice_cannot_edit": "ویرایش مشخصات فردی بعد از تکمیل اطلاعات امکان پذیر نیست",
"notice_cannot_edit_legal": "ویرایش مشخصات حقوقی بعد از تکمیل اطلاعات امکان پذیر نیست"
}, },
"man": "مرد", "man": "مرد",
"woman": "زن", "woman": "زن",
+18 -6
View File
@@ -1,13 +1,17 @@
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import StatusWithText from '../../components/StatusWithText' import StatusWithText from '../../components/StatusWithText'
import { useNavigate } from 'react-router-dom' import { useNavigate, useParams } from 'react-router-dom'
import { ArrowLeft2 } from 'iconsax-react' import { ArrowLeft2 } from 'iconsax-react'
import { useGetAnnoncementDetail } from './hooks/useAnnoncementData'
import PageLoading from '../../components/PageLoading'
const AnnouncementDetail: FC = () => { const AnnouncementDetail: FC = () => {
const { id } = useParams()
const { t } = useTranslation('global') const { t } = useTranslation('global')
const navigate = useNavigate() const navigate = useNavigate()
const getAnnoncementDetail = useGetAnnoncementDetail(id ? id : '')
return ( return (
<div className='mt-4'> <div className='mt-4'>
@@ -21,21 +25,29 @@ const AnnouncementDetail: FC = () => {
</div> </div>
</div> </div>
{
getAnnoncementDetail.isPending ?
<PageLoading />
:
<div className='flex-1 mt-4 bg-white py-6 px-4 rounded-2xl'> <div className='flex-1 mt-4 bg-white py-6 px-4 rounded-2xl'>
<div className='flex justify-between items-start'> <div className='flex justify-between items-start'>
<div className='text-sm max-w-[70%] leading-67'> <div className='text-sm max-w-[70%] leading-67'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ {getAnnoncementDetail.data?.data?.announcement?.title}
</div> </div>
{
getAnnoncementDetail.data?.data?.announcement.isImportant &&
<StatusWithText <StatusWithText
variant='success' variant='error'
text={t('announcement.new')} text={t('announcement.important')}
/> />
}
</div> </div>
<div className='mt-5 leading-7 text-xs font-light'> <div dangerouslySetInnerHTML={{ __html: getAnnoncementDetail.data?.data?.announcement?.content }} className='mt-5 leading-7 text-xs font-light'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را می طلبد، تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی، و فرهنگ پیشرو در زبان فارسی ایجاد کرد، در این صورت می توان امید داشت که تمام و دشواری موجود در ارائه راهکارها، و شرایط سخت تایپ به پایان رسد و زمان مورد نیاز شامل حروفچینی دستاوردهای اصلی، و جوابگوی سوالات پیوسته اهل دنیای موجود طراحی اساسا مورد استفاده قرار گیرد.
</div> </div>
</div> </div>
}
</div> </div>
) )
} }
+43 -49
View File
@@ -1,23 +1,28 @@
import { FC, Fragment } from 'react' import { FC, Fragment, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import Input from '../../components/Input' import Input from '../../components/Input'
import Select from '../../components/Select'
import StatusWithText from '../../components/StatusWithText' import StatusWithText from '../../components/StatusWithText'
import { clx } from '../../helpers/utils' import { clx } from '../../helpers/utils'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Pages } from '../../config/Pages' import { Pages } from '../../config/Pages'
import { useGetAnnoncement } from './hooks/useAnnoncementData' import { useGetAnnoncement } from './hooks/useAnnoncementData'
import PageLoading from '../../components/PageLoading' import PageLoading from '../../components/PageLoading'
import { AnnoncementItemType } from './types/AnnoncementTypes'
import moment from 'moment-jalaali'
const AnnouncementtList: FC = () => { const AnnouncementtList: FC = () => {
const getAnnoncements = useGetAnnoncement()
const { t } = useTranslation('global')
const navigate = useNavigate() const navigate = useNavigate()
const { t } = useTranslation('global')
const [search, setSearch] = useState<string>('')
const [itemSelected, setItemSelected] = useState<AnnoncementItemType>()
const getAnnoncements = useGetAnnoncement(search)
const handleChange = () => { const handleChange = (item: AnnoncementItemType) => {
if (window.innerWidth < 1280) { if (window.innerWidth < 1280) {
navigate(Pages.announcement.detail + '1') navigate(Pages.announcement.detail + item.id)
} else {
setItemSelected(item)
} }
} }
@@ -35,10 +40,11 @@ const AnnouncementtList: FC = () => {
variant='search' variant='search'
placeholder={t('announcement.search')} placeholder={t('announcement.search')}
className='-mt-1' className='-mt-1'
onChangeSearchFinal={(value) => setSearch(value)}
/> />
</div> </div>
<div> {/* <div>
<Select <Select
items={[ items={[
{ value: '1', label: 'همه' }, { value: '1', label: 'همه' },
@@ -47,98 +53,86 @@ const AnnouncementtList: FC = () => {
]} ]}
className='bg-[#EEF0F7] w-[68px]' className='bg-[#EEF0F7] w-[68px]'
/> />
</div> </div> */}
</div> </div>
{ {
getAnnoncements.isPending ? getAnnoncements.isPending ?
<div className='mt-8'>
<PageLoading /> <PageLoading />
</div>
: :
<Fragment> <Fragment>
<div className='mt-8'> <div className='mt-8'>
<div onClick={handleChange} className={clx( {
getAnnoncements.data?.data?.announcements?.map((item: AnnoncementItemType) => {
return (
<div onClick={() => handleChange(item)} className={clx(
'p-4 rounded-2xl', 'p-4 rounded-2xl',
'bg-[#F8F9FB]' item.id === itemSelected?.id && 'bg-[#F8F9FB]'
)}> )}>
<div className='flex justify-between'> <div className='flex justify-between'>
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'> <div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ {item.title}
</div> </div>
<div className='flex gap-1'> <div className='flex gap-1'>
{
item.isImportant &&
<StatusWithText <StatusWithText
variant='error' variant='error'
text={t('announcement.important')} text={t('announcement.important')}
/> />
<StatusWithText }
{/* <StatusWithText
variant='success' variant='success'
text={t('announcement.new')} text={t('announcement.new')}
/> /> */}
</div> </div>
</div> </div>
<div className='m-2 flex justify-between items-center text-[11px]'> <div className='m-2 flex justify-between items-center text-[11px]'>
<div className='max-w-[70%] truncate text-description'> <div dangerouslySetInnerHTML={{ __html: item.content }} className='max-w-[70%] truncate text-description'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
</div> </div>
<div className='text-description'> <div className='text-description'>
۱۴۰۳/۰۹/۲۴ {moment(item.publishAt, 'jYYYY/jMM/jDD').format('jYYYY/jMM/jDD')}
</div> </div>
</div> </div>
</div> </div>
<div onClick={handleChange} className={clx( )
'p-4 rounded-2xl', })
)}> }
<div className='flex justify-between'>
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
</div>
<div className='flex gap-1'>
<StatusWithText
variant='error'
text={t('announcement.important')}
/>
<StatusWithText
variant='success'
text={t('announcement.new')}
/>
</div>
</div>
<div className='m-2 flex justify-between items-center text-[11px]'>
<div className='max-w-[70%] truncate text-description'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ
</div>
<div className='text-description'>
۱۴۰۳/۰۹/۲۴
</div>
</div>
</div>
</div> </div>
</Fragment> </Fragment>
} }
</div> </div>
{
itemSelected &&
<div className='flex-1 hidden xl:block bg-white py-7 px-5 rounded-2xl'> <div className='flex-1 hidden xl:block bg-white py-7 px-5 rounded-2xl'>
<div className='flex justify-between items-center'> <div className='flex justify-between items-center'>
<div className='text-lg'> <div className='text-lg'>
متن اطلاعیه متن اطلاعیه
</div> </div>
{
itemSelected.isImportant &&
<StatusWithText <StatusWithText
variant='success' variant='error'
text={t('announcement.new')} text={t('announcement.important')}
/> />
}
</div> </div>
<div className='mt-8'> <div className='mt-8'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ {itemSelected.title}
</div> </div>
<div className='mt-5 leading-7 text-xs font-light'> <div dangerouslySetInnerHTML={{ __html: itemSelected.content }} className='mt-5 leading-7 text-xs font-light'>
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است، و برای شرایط فعلی تکنولوژی مورد نیاز، و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد، کتابهای زیادی در شصت و سه درصد گذشته حال و آینده، شناخت فراوان جامعه و متخصصان را می طلبد، تا با نرم افزارها شناخت بیشتری را برای طراحان رایانه ای علی الخصوص طراحان خلاقی، و فرهنگ پیشرو در زبان فارسی ایجاد کرد، در این صورت می توان امید داشت که تمام و دشواری موجود در ارائه راهکارها، و شرایط سخت تایپ به پایان رسد و زمان مورد نیاز شامل حروفچینی دستاوردهای اصلی، و جوابگوی سوالات پیوسته اهل دنیای موجود طراحی اساسا مورد استفاده قرار گیرد.
</div> </div>
</div> </div>
}
</div> </div>
</div> </div>
@@ -1,9 +1,17 @@
import * as api from "../service/AnnoncementService"; import * as api from "../service/AnnoncementService";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
export const useGetAnnoncement = () => { export const useGetAnnoncement = (search: string) => {
return useQuery({ return useQuery({
queryKey: ["announcements"], queryKey: ["announcements", search],
queryFn: () => api.getAnnoncements(), queryFn: () => api.getAnnoncements(search),
});
};
export const useGetAnnoncementDetail = (id: string) => {
return useQuery({
queryKey: ["announcement", id],
queryFn: () => api.getAnnoncementDetail(id),
enabled: !!id,
}); });
}; };
@@ -1,6 +1,11 @@
import axios from "../../../config/axios"; import axios from "../../../config/axios";
export const getAnnoncements = async () => { export const getAnnoncements = async (search: string) => {
const { data } = await axios.get(`/announcements/user`); const { data } = await axios.get(`/announcements/user?q=${search}`);
return data;
};
export const getAnnoncementDetail = async (id: string) => {
const { data } = await axios.get(`/announcements/${id}`);
return data; return data;
}; };
@@ -0,0 +1,16 @@
export type AnnoncementItemType = {
content: string;
createdAt: string;
id: string;
isImportant: boolean;
isPublic: boolean;
isRead: boolean;
publishAt: string;
service: {
id: string;
name: string;
};
targetGroups: any[];
title: string;
updatedAt: string;
};
+1 -1
View File
@@ -191,7 +191,7 @@ const Legal: FC = () => {
size={18} size={18}
color='#DB0105' color='#DB0105'
/> />
<div>{t('financial.notice_cannot_edit')}</div> <div>{t('financial.notice_cannot_edit_legal')}</div>
</div> </div>
{ {
+31 -23
View File
@@ -1,15 +1,20 @@
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { Edit } from 'iconsax-react' import { Edit } from 'iconsax-react'
import StatusCircle from '../../components/StatusCircle'
import SamanImage from '../../assets/images/saman.png' import SamanImage from '../../assets/images/saman.png'
import { useParams } from 'react-router-dom' import { useParams } from 'react-router-dom'
import { useGetInvoiceDetail } from './hooks/useReceiptData' import { useGetInvoiceDetail } from './hooks/useReceiptData'
import { useGetFinancialInfo } from '../financial/hooks/useFinancialData'
import { ReceiptDetailItemType } from './types/ReceiptTypes'
import { NumberFormat } from '../../config/func'
import moment from 'moment-jalaali'
import PageLoading from '../../components/PageLoading'
const ReceiptsDetail: FC = () => { const ReceiptsDetail: FC = () => {
const { id } = useParams() const { id } = useParams()
const { t } = useTranslation('global') const { t } = useTranslation('global')
const getProfile = useGetFinancialInfo()
const getInvoce = useGetInvoiceDetail(id ? id : '') const getInvoce = useGetInvoiceDetail(id ? id : '')
return ( return (
@@ -19,6 +24,10 @@ const ReceiptsDetail: FC = () => {
۱۲۳۱۲ ۱۲۳۱۲
</div> </div>
{
getInvoce.isPending || getProfile.isPending ?
<PageLoading />
:
<div className='flex xl:flex-row flex-col mt-8 gap-6'> <div className='flex xl:flex-row flex-col mt-8 gap-6'>
<div className='flex-1'> <div className='flex-1'>
<div className='w-full bg-white rounded-3xl p-8'> <div className='w-full bg-white rounded-3xl p-8'>
@@ -143,14 +152,6 @@ const ReceiptsDetail: FC = () => {
<div> <div>
{t('receip.detail_receip')} {t('receip.detail_receip')}
</div> </div>
<div className='flex gap-2 text-xs items-center'>
<StatusCircle
color='#FF7B00'
/>
<div>
{t('receip.not_paid')}
</div>
</div>
</div> </div>
<div className='mt-7 text-sm'> <div className='mt-7 text-sm'>
@@ -162,20 +163,29 @@ const ReceiptsDetail: FC = () => {
{t('receip.price')} {t('receip.price')}
</div> </div>
</div> </div>
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'> {
<div> getInvoce.data?.data?.invoice?.items?.map((item: ReceiptDetailItemType) => {
اکانت یکساله با دسترسی return (
<div key={item.id} className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
<div className='flex gap-0.5'>
<span>{item.name}</span>
<span>{item.count}</span>
<span>عدد</span>
</div> </div>
<div> <div>
۱۰,۰۰۰,۰۰۰ ریال <span>{NumberFormat(item.totalPrice)}</span> ریال
</div> </div>
</div> </div>
)
})
}
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'> <div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
<div> <div>
۱۰٪ ارزش افزوده ۱۰٪ ارزش افزوده
</div> </div>
<div> <div>
۱۰,۰۰۰,۰۰۰ ریال <span>{NumberFormat(getInvoce.data?.data?.invoice?.tax)}</span> ریال
</div> </div>
</div> </div>
<div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'> <div className='flex justify-between items-center text-xs border-t border-[#EAEDF5] h-14'>
@@ -183,7 +193,7 @@ const ReceiptsDetail: FC = () => {
جمع کل جمع کل
</div> </div>
<div> <div>
۱۰,۰۰۰,۰۰۰ ریال <span>{NumberFormat(getInvoce.data?.data?.invoice?.totalPrice)}</span> ریال
</div> </div>
</div> </div>
</div> </div>
@@ -195,10 +205,7 @@ const ReceiptsDetail: FC = () => {
<div> <div>
{t('receip.receip_information')} {t('receip.receip_information')}
</div> </div>
<div className='flex gap-2 text-xs items-center'> <div className='border border-orange-400 text-orange-400 rounded-lg text-[9px] h-6 flex items-center px-1'>
<StatusCircle
color='#FF7B00'
/>
<div>پرداخت نشده</div> <div>پرداخت نشده</div>
</div> </div>
</div> </div>
@@ -209,7 +216,7 @@ const ReceiptsDetail: FC = () => {
{t('receip.date_factor')} {t('receip.date_factor')}
</div> </div>
<div> <div>
۱۴۰۲/۱۰/۰۴ {moment(getInvoce.data?.data?.invoice?.createdAt).format('jYYYY-jMM-jDD')}
</div> </div>
</div> </div>
<div className='flex justify-between h-12 border-b items-center'> <div className='flex justify-between h-12 border-b items-center'>
@@ -217,7 +224,7 @@ const ReceiptsDetail: FC = () => {
{t('receip.due_date')} {t('receip.due_date')}
</div> </div>
<div> <div>
۱۴۰۲/۱۰/۰۴ {moment(getInvoce.data?.data?.invoice?.dueDate).format('jYYYY-jMM-jDD')}
</div> </div>
</div> </div>
<div className='flex justify-between h-12 border-b items-center'> <div className='flex justify-between h-12 border-b items-center'>
@@ -225,7 +232,7 @@ const ReceiptsDetail: FC = () => {
{t('receip.factor_number')} {t('receip.factor_number')}
</div> </div>
<div> <div>
۱۲۳۱ {getInvoce.data?.data?.invoice.id}
</div> </div>
</div> </div>
<div className='flex justify-between h-12 border-b items-center'> <div className='flex justify-between h-12 border-b items-center'>
@@ -233,7 +240,7 @@ const ReceiptsDetail: FC = () => {
{t('receip.status_factor')} {t('receip.status_factor')}
</div> </div>
<div> <div>
تایید شده {t(`receip.${getInvoce.data?.data?.invoice?.status}`)}
</div> </div>
</div> </div>
</div> </div>
@@ -254,6 +261,7 @@ const ReceiptsDetail: FC = () => {
</div> </div>
</div> </div>
</div> </div>
}
</div> </div>
) )
+12
View File
@@ -17,3 +17,15 @@ export type ReceiptItemType = {
status: "PENDING" | "PAID" | "CANCELED" | "EXPIRED"; status: "PENDING" | "PAID" | "CANCELED" | "EXPIRED";
paidAt: string; paidAt: string;
}; };
export type ReceiptDetailItemType = {
id: string;
createdAt: string;
updatedAt: string;
name: string;
count: string | number;
unitPrice: string | number;
discount: string | number;
totalPrice: number;
subscriptionPlan?: string | null;
};
+10 -6
View File
@@ -16,6 +16,8 @@ import { toast } from 'react-toastify'
import { ErrorType } from '../../helpers/types' import { ErrorType } from '../../helpers/types'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { Pages } from '../../config/Pages' import { Pages } from '../../config/Pages'
import { useGetMyServices } from '../service/hooks/useServiceData'
import { MyServicesItem } from '../service/types/ServiecTypes'
const CreateTicket: FC = () => { const CreateTicket: FC = () => {
@@ -26,6 +28,7 @@ const CreateTicket: FC = () => {
const getCategories = useGetCategoriesTicket() const getCategories = useGetCategoriesTicket()
const multiUpload = useMultiUpload() const multiUpload = useMultiUpload()
const createTicket = useCreateTicket() const createTicket = useCreateTicket()
const getMyservice = useGetMyServices('')
const formik = useFormik<CreateTicketType>({ const formik = useFormik<CreateTicketType>({
initialValues: { initialValues: {
@@ -143,15 +146,16 @@ const CreateTicket: FC = () => {
<div className='mt-6 rowTwoInput'> <div className='mt-6 rowTwoInput'>
<Select <Select
label={t('ticket.select_your_service')} label={t('ticket.select_your_service')}
items={[
{
label: '',
value: ''
}
]}
placeholder={t('ticket.not_once')} placeholder={t('ticket.not_once')}
className='border' className='border'
name='danakServiceId' name='danakServiceId'
items={getMyservice.data?.data?.subscriptions?.map((item: MyServicesItem) => ({
label: item.plan?.service?.name,
value: item.plan?.service.id
})
)}
onChange={formik.handleChange}
error_text={formik.touched.danakServiceId && formik.errors.danakServiceId ? formik.errors.danakServiceId : ''}
/> />
<Select <Select
label={t('ticket.category')} label={t('ticket.category')}
+3 -1
View File
@@ -71,7 +71,8 @@ const TicketList: FC = () => {
<tr> <tr>
<Td text={t('ticket.number')} /> <Td text={t('ticket.number')} />
<Td text={t('ticket.title')} /> <Td text={t('ticket.title')} />
<Td text={t('ticket.team')} /> <Td text={t('ticket.category')} />
<Td text={t('ticket.service')} />
<Td text={t('ticket.date')} /> <Td text={t('ticket.date')} />
<Td text={t('ticket.status')} /> <Td text={t('ticket.status')} />
<Td text={t('ticket.priority')} /> <Td text={t('ticket.priority')} />
@@ -86,6 +87,7 @@ const TicketList: FC = () => {
<Td text={item.numericId} /> <Td text={item.numericId} />
<Td text={item.subject} /> <Td text={item.subject} />
<Td text={item?.category?.title} /> <Td text={item?.category?.title} />
<Td text={item?.danakService?.name} />
<Td text={''}> <Td text={''}>
<div className='dltr text-right'> <div className='dltr text-right'>
{moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')} {moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')}