skeleton logout and ...

This commit is contained in:
hamid zarghami
2025-03-11 12:51:37 +03:30
parent c6a66eaf23
commit 8182e15cd7
12 changed files with 248 additions and 194 deletions
+1 -1
View File
@@ -82,7 +82,7 @@ define(['./workbox-e7681877'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.qrm0hc87vi" "revision": "0.l1l8csoi5qg"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
+49 -49
View File
@@ -6,10 +6,10 @@ 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 { AnnoncementItemType } from './types/AnnoncementTypes' import { AnnoncementItemType } from './types/AnnoncementTypes'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import AnouncementSkeleton from './components/AnouncementSkeleton'
const AnnouncementtList: FC = () => { const AnnouncementtList: FC = () => {
@@ -62,59 +62,59 @@ const AnnouncementtList: FC = () => {
</div> */} </div> */}
</div> </div>
{ <Fragment>
getAnnoncements.isPending ? <div className='mt-8'>
<div className='mt-8'> {
<PageLoading /> getAnnoncements.isPending ?
</div> <Fragment>
: <AnouncementSkeleton />
<Fragment> <AnouncementSkeleton />
<div className='mt-8'> <AnouncementSkeleton />
{ </Fragment>
getAnnoncements.data?.data?.announcements?.map((item: AnnoncementItemType) => { :
return ( getAnnoncements.data?.data?.announcements?.map((item: AnnoncementItemType) => {
<div onClick={() => handleChange(item)} className={clx( return (
'p-4 border-b', <div onClick={() => handleChange(item)} className={clx(
item.id === itemSelected?.id && 'bg-[#F8F9FB]' 'p-4 border-b cursor-pointer',
)}> item.id === itemSelected?.id && 'bg-[#F8F9FB]'
<div className='flex justify-between'> )}>
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'> <div className='flex justify-between'>
{item.announcement.title} <div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
</div> {item.announcement.title}
<div className='flex gap-1'>
{
item.announcement.isImportant &&
<StatusWithText
variant='error'
text={t('announcement.important')}
/>
}
{
!item.isRead &&
<StatusWithText
variant='success'
text={t('announcement.new')}
/>
}
</div>
</div> </div>
<div className='m-2 flex justify-between items-center text-[11px]'> <div className='flex gap-1'>
<div dangerouslySetInnerHTML={{ __html: item.announcement.content }} className='max-w-[70%] truncate text-description'> {
</div> item.announcement.isImportant &&
<div className='text-description'> <StatusWithText
{moment(item.announcement.publishAt).format('jYYYY/jMM/jDD')} variant='error'
</div> text={t('announcement.important')}
/>
}
{
!item.isRead &&
<StatusWithText
variant='success'
text={t('announcement.new')}
/>
}
</div> </div>
</div> </div>
)
})
}
</div> <div className='m-2 flex justify-between items-center text-[11px]'>
</Fragment> <div dangerouslySetInnerHTML={{ __html: item.announcement.content }} className='max-w-[70%] truncate text-description'>
} </div>
<div className='text-description'>
{moment(item.announcement.publishAt).format('jYYYY/jMM/jDD')}
</div>
</div>
</div>
)
})
}
</div>
</Fragment>
</div> </div>
{ {
@@ -0,0 +1,41 @@
import { FC } from 'react'
import Skeleton from 'react-loading-skeleton'
const AnouncementSkeleton: FC = () => {
return (
<div className={'p-4 border-b'}>
<div className='flex items-center justify-between'>
<div className='leading-6 text-xs xl:text-[12.5px] max-w-[70%]'>
<Skeleton
height={7}
width={100}
/>
</div>
<div className='flex gap-1'>
<Skeleton
className='!w-10 !h-6 !rounded-full'
/>
<Skeleton
className='!w-10 !h-6 !rounded-full'
/>
</div>
</div>
<div className='m-2 flex justify-between items-center text-[11px]'>
<Skeleton
height={7}
width={150}
/>
<div className='text-description'>
<Skeleton
height={7}
width={40}
/>
</div>
</div>
</div>
)
}
export default AnouncementSkeleton
+6
View File
@@ -37,3 +37,9 @@ export const useRegister = () => {
mutationFn: (variables: RegisterType) => api.register(variables), mutationFn: (variables: RegisterType) => api.register(variables),
}); });
}; };
export const useLogout = () => {
return useMutation({
mutationFn: (_) => api.logout(),
});
};
+5
View File
@@ -43,3 +43,8 @@ export const refreshToken = async (params: RefreshTokenType) => {
const { data } = await axios.post(`/auth/refresh`, params); const { data } = await axios.post(`/auth/refresh`, params);
return data; return data;
}; };
export const logout = async () => {
const { data } = await axios.post(`/auth/logout`);
return data;
};
+64 -66
View File
@@ -6,11 +6,11 @@ import Td from '../../components/Td'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { Pages } from '../../config/Pages' import { Pages } from '../../config/Pages'
import { useGetInvoices } from './hooks/useReceiptData' import { useGetInvoices } from './hooks/useReceiptData'
import PageLoading from '../../components/PageLoading'
import { NumberFormat } from '../../config/func' import { NumberFormat } from '../../config/func'
import { ReceiptItemType } from './types/ReceiptTypes' import { ReceiptItemType } from './types/ReceiptTypes'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import DefaultTableSkeleton from '../../components/DefaultTableSkeleton'
const ReceiptsList: FC = () => { const ReceiptsList: FC = () => {
@@ -63,73 +63,71 @@ const ReceiptsList: FC = () => {
/> />
</div> </div>
{
getInvoices.isPending ?
<div className='mt-5'>
<PageLoading />
</div>
:
<div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
<table className='w-full text-sm '>
<thead className='thead'>
<tr>
<Td text={t('ticket.number')} />
{/* <Td text={t('receip.customer')} /> */}
<Td text={t('receip.date_receipt')} />
<Td text={t('receip.last_date_receipt')} />
<Td text={t('receip.total')} />
<Td text={t('receip.service')} />
<Td text={t('receip.status')} />
<Td text={t('receip.status_paid')} />
<Td text={''} />
</tr>
</thead>
<tbody>
{
getInvoices.data?.data?.invoices?.map((item: ReceiptItemType) => {
return (
<tr className='tr'>
<Td text={moment(item.createdAt).format('jYYYY') + item.numericId} />
{/* <Td text={item.user.firstName + ' ' + item.user.lastName} /> */}
<Td text={''}>
<div className='dltr text-right'>
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
</Td>
<Td text={''}> <div className='relative overflow-x-auto rounded-3xl mt-9 w-full'>
<div className='dltr text-right'> <table className='w-full text-sm '>
{moment(item.dueDate).format('jYYYY-jMM-jDD HH:mm')} <thead className='thead'>
</div> <tr>
</Td> <Td text={t('ticket.number')} />
<Td text={NumberFormat(item.totalPrice)} /> {/* <Td text={t('receip.customer')} /> */}
<Td text={''}> <Td text={t('receip.date_receipt')} />
<div className='flex flex-wrap gap-1.5'> <Td text={t('receip.last_date_receipt')} />
{ <Td text={t('receip.total')} />
item.items.map((item => { <Td text={t('receip.service')} />
return ( <Td text={t('receip.status')} />
<div className='bg-primary p-2 rounded-full text-white flex gap-1'><span>{item.name}</span> <span>({item.count})</span></div> <Td text={t('receip.status_paid')} />
) <Td text={''} />
})) </tr>
} </thead>
</div> <tbody>
</Td> {
<Td text={t(`receip.${item.status}`)} />
<Td text={item.status === 'PAID' ? moment(item.paidAt).format('jYYYY-jMM-jDD') : t('receip.not_paid')} />
<Td text={''}>
<Link to={Pages.receipts.detail + item.id}>
<Eye size={20} color='black' />
</Link>
</Td>
</tr>
)
})
}
</tbody> getInvoices.isPending ?
</table> <DefaultTableSkeleton tdCount={8} />
</div> :
} getInvoices.data?.data?.invoices?.map((item: ReceiptItemType) => {
return (
<tr className='tr'>
<Td text={moment(item.createdAt).format('jYYYY') + item.numericId} />
{/* <Td text={item.user.firstName + ' ' + item.user.lastName} /> */}
<Td text={''}>
<div className='dltr text-right'>
{moment(item.createdAt).format('jYYYY-jMM-jDD HH:mm')}
</div>
</Td>
<Td text={''}>
<div className='dltr text-right'>
{moment(item.dueDate).format('jYYYY-jMM-jDD HH:mm')}
</div>
</Td>
<Td text={NumberFormat(item.totalPrice)} />
<Td text={''}>
<div className='flex flex-wrap gap-1.5'>
{
item.items.map((item => {
return (
<div className='bg-primary p-2 rounded-full text-white flex gap-1'><span>{item.name}</span> <span>({item.count})</span></div>
)
}))
}
</div>
</Td>
<Td text={t(`receip.${item.status}`)} />
<Td text={item.status === 'PAID' ? moment(item.paidAt).format('jYYYY-jMM-jDD') : t('receip.not_paid')} />
<Td text={''}>
<Link to={Pages.receipts.detail + item.id}>
<Eye size={20} color='black' />
</Link>
</Td>
</tr>
)
})
}
</tbody>
</table>
</div>
</div> </div>
) )
} }
+17 -13
View File
@@ -4,13 +4,14 @@ import { useTranslation } from 'react-i18next'
import { ArrowLeft } from 'iconsax-react' import { ArrowLeft } from 'iconsax-react'
import TitleLine from '../../components/TitleLine' import TitleLine from '../../components/TitleLine'
import { useGetSuggestedServices } from './hooks/useServiceData' import { useGetSuggestedServices } from './hooks/useServiceData'
import PageLoading from '../../components/PageLoading'
import { ItemServiceType } from './types/ServiecTypes' import { ItemServiceType } from './types/ServiecTypes'
import ServiceItem from '../../components/ServiceItem' import ServiceItem from '../../components/ServiceItem'
import OtherServicesComponent from './components/OtherServices' import OtherServicesComponent from './components/OtherServices'
import { useGetAds } from '../ads/hooks/useAdsData' import { useGetAds } from '../ads/hooks/useAdsData'
import { AdsDisplayLocation, AdsItemType } from '../ads/types/AdsTypes' import { AdsDisplayLocation, AdsItemType } from '../ads/types/AdsTypes'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import Skeleton from 'react-loading-skeleton'
import OtherServiceSkeleton from './components/OtherServiceSkeleton'
const OtherServices: FC = () => { const OtherServices: FC = () => {
@@ -30,7 +31,7 @@ const OtherServices: FC = () => {
<div className='flex-1'> <div className='flex-1'>
{ {
getSuggestedServices.isPending || getAdsLeft.isPending ? getSuggestedServices.isPending || getAdsLeft.isPending ?
<PageLoading /> <OtherServiceSkeleton />
: :
<Fragment> <Fragment>
<Carousel autoplay className="rounded-3xl overflow-hidden h-fit dltr z-[1]" placeholder="" onPointerEnterCapture={() => { }} onPointerLeaveCapture={() => { }} > <Carousel autoplay className="rounded-3xl overflow-hidden h-fit dltr z-[1]" placeholder="" onPointerEnterCapture={() => { }} onPointerLeaveCapture={() => { }} >
@@ -96,19 +97,22 @@ const OtherServices: FC = () => {
</div> </div>
{ {
getAdsLeft.isSuccess && getAdsLeft.data.data.ads[0] && getAdsLeft.isPending ?
<a href={getAdsLeft.data?.data?.ads[0].link} target='_blank' className='bg-white w-sidebar h-fit hidden xl:block rounded-3xl overflow-hidden sticky top-0'> <Skeleton className='!w-sidebar !rounded-3xl !hidden xl:!block' height={550} />
<img :
src={getAdsLeft.data.data.ads[0].imageUrl} getAdsLeft.isSuccess && getAdsLeft.data.data.ads[0] &&
className='w-full backdrop-blur-[100px] h-[550px] object-cover' <a href={getAdsLeft.data?.data?.ads[0].link} target='_blank' className='bg-white w-sidebar h-fit hidden xl:block rounded-3xl overflow-hidden sticky top-0'>
/> <img
src={getAdsLeft.data.data.ads[0].imageUrl}
className='w-full backdrop-blur-[100px] h-[550px] object-cover'
/>
<div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-black bg-opacity-5 backdrop-blur-[14px]'> <div className='absolute flex items-center px-4 w-full bottom-0 h-[86px] bg-black bg-opacity-5 backdrop-blur-[14px]'>
<div className='max-w-[80%] text-sm leading-6 text-white'> <div className='max-w-[80%] text-sm leading-6 text-white'>
{getAdsLeft.data.data.ads[0].title} {getAdsLeft.data.data.ads[0].title}
</div>
</div> </div>
</div> </a>
</a>
} }
</div> </div>
) )
@@ -0,0 +1,32 @@
import { FC } from 'react'
import Skeleton from 'react-loading-skeleton'
import TitleLine from '../../../components/TitleLine'
import { useTranslation } from 'react-i18next'
import ServiceItemSkeleton from './ServiceItemSkeleton'
const OtherServiceSkeleton: FC = () => {
const { t } = useTranslation('global')
return (
<div>
<Skeleton
height={300}
className='!rounded-3xl'
/>
<div className='w-full mt-8'>
<TitleLine title={t('service.selected_danak')} />
</div>
<div className='flex flex-wrap xl:gap-6 gap-4 items-center mt-8'>
<ServiceItemSkeleton />
<ServiceItemSkeleton />
<ServiceItemSkeleton />
<ServiceItemSkeleton />
</div>
</div>
)
}
export default OtherServiceSkeleton
+2 -42
View File
@@ -68,51 +68,11 @@ const OtherServicesComponent: FC = () => {
}) })
} }
<div className='flex-1 min-w-[40%] xl:min-w-[30%] xl:p-6 p-4'></div> <div className='flex-1 min-w-[40%] xl:min-w-[30%] xl:p-6 p-4'></div>
<div className='flex-1 min-w-[40%] xl:min-w-[30%] xl:p-6 p-4'></div>
</div> </div>
</div> </div>
</div>
{/* <a target='_blank' key={item.id} href={item.status === 'ACTIVE' ? item.plan.service.link + item.id : '#'} className={clx(
'flex-1 min-w-[40%] xl:min-w-[20%] bg-white rounded-3xl p-6 flex flex-col items-center self-stretch',
item.status === 'INACTIVE' && 'opacity-50'
)}>
<div className="flex-grow">
<ServiceSection
item={{
author: item.plan.service.author,
createDate: item.plan?.service?.createDate,
createdAt: item.createdAt,
description: item.plan?.service?.title,
icon: item.plan?.service.icon,
id: item.plan.service.id,
isActive: item.plan.isActive,
isDanakSuggest: item.plan.service.isDanakSuggest,
link: item.plan.service.link,
metaDescription: item.plan.service.metaDescription,
name: item.plan.service.name,
serviceLanguage: item.plan.service.serviceLanguage,
softwareLanguage: item.plan.service.softwareLanguage,
updatedAt: item.plan.service.updatedAt,
userCount: item.plan.service?.userCount,
title: item.plan.service.title,
}}
/>
</div>
<div className='mt-6 flex gap-1 items-center'>
<StatusCircle
color={item.status === 'ACTIVE' ? '#00BA4B' : '#FF0000'}
/>
<div className='text-xs text-description'>
{
item.status === 'ACTIVE' ?
t('active')
:
t('inactive')
}
</div>
</div>
</a> */}
</div >
) )
} }
+25 -21
View File
@@ -9,6 +9,7 @@ import { Pages } from '../../config/Pages'
import { useGetTickets } from './hooks/useTicketData' import { useGetTickets } from './hooks/useTicketData'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { Helmet } from 'react-helmet-async' import { Helmet } from 'react-helmet-async'
import DefaultTableSkeleton from '../../components/DefaultTableSkeleton'
const TicketList: FC = () => { const TicketList: FC = () => {
@@ -88,27 +89,30 @@ const TicketList: FC = () => {
</thead> </thead>
<tbody> <tbody>
{ {
getTickets.data?.data?.tickets?.map((item: any) => ( getTickets.isPending ?
<tr key={item.id} className='tr'> <DefaultTableSkeleton tdCount={9} />
<Td text={item.numericId} /> :
<Td text={item.subject} /> getTickets.data?.data?.tickets?.map((item: any) => (
<Td text={item?.category?.title} /> <tr key={item.id} className='tr'>
<Td text={item?.danakService?.name} /> <Td text={item.numericId} />
<Td text={''}> <Td text={item.subject} />
<div className='dltr text-right'> <Td text={item?.category?.title} />
{moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')} <Td text={item?.danakService?.name} />
</div> <Td text={''}>
</Td> <div className='dltr text-right'>
<Td text={t(`ticket.${item.status}`)} /> {moment(item.updatedAt).format('jYYYY-jMM-jDD HH:mm')}
<Td text={t(`ticket.${item.priority}`)} /> </div>
<Td text={''}> </Td>
<Link to={Pages.ticket.detail + item.id}> <Td text={t(`ticket.${item.status}`)} />
<Eye size={20} color='#8C90A3' /> <Td text={t(`ticket.${item.priority}`)} />
</Link> <Td text={''}>
</Td> <Link to={Pages.ticket.detail + item.id}>
<Td text={''} /> <Eye size={20} color='#8C90A3' />
</tr> </Link>
)) </Td>
<Td text={''} />
</tr>
))
} }
</tbody> </tbody>
</table> </table>
+5 -1
View File
@@ -2,6 +2,7 @@ import { FC, ReactNode } from 'react'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { clx } from '../helpers/utils' import { clx } from '../helpers/utils'
import { Pages } from '../config/Pages' import { Pages } from '../config/Pages'
import { useLogout } from '../pages/auth/hooks/useAuthData'
type Props = { type Props = {
icon: ReactNode, icon: ReactNode,
@@ -14,7 +15,10 @@ type Props = {
const SideBarItem: FC<Props> = (props: Props) => { const SideBarItem: FC<Props> = (props: Props) => {
const handleLogout = () => { const logout = useLogout()
const handleLogout = async () => {
await logout.mutateAsync()
localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME) localStorage.removeItem(import.meta.env.VITE_TOKEN_NAME)
window.location.href = Pages.auth.login window.location.href = Pages.auth.login
} }
+1 -1
View File
@@ -9,7 +9,7 @@ export default defineConfig({
VitePWA({ VitePWA({
registerType: "autoUpdate", registerType: "autoUpdate",
devOptions: { devOptions: {
enabled: true, // در حالت توسعه هم کار کند enabled: false, // در حالت توسعه هم کار کند
}, },
manifest: { manifest: {
name: "Danak Console", name: "Danak Console",