request + detail + change status request
This commit is contained in:
@@ -3,6 +3,7 @@ VITE_REFRESH_TOKEN_NAME = 'dsc_refresh_token'
|
||||
# VITE_BASE_URL = 'http://192.168.1.113:4001'
|
||||
VITE_BASE_URL = 'https://api-dzone.danakcorp.com'
|
||||
# VITE_DANAK_BASE_URL ='http://192.168.1.113:4000'
|
||||
VITE_DANAK_BASE_URL ='https://api.danakcorp.com'
|
||||
VITE_SERVICE_ID = '7e3c2f08-b7e7-4402-ae5f-fea99cff56bd'
|
||||
VITE_WORKSPACE_ID = 'workspace_id'
|
||||
VITE_LOGIN_URL = 'https://console.danakcorp.com/auth/login'
|
||||
|
||||
@@ -6,6 +6,7 @@ export const Pages = {
|
||||
},
|
||||
requests: {
|
||||
list: "/requests/list",
|
||||
detail: "/requests/detail/",
|
||||
},
|
||||
dashboard: "/dashboard",
|
||||
services: {
|
||||
|
||||
@@ -843,5 +843,66 @@
|
||||
"select_company": "انتخاب شرکت",
|
||||
"register_date": "تاریخ ثبت نام",
|
||||
"company": "شرکت"
|
||||
},
|
||||
"request": {
|
||||
"request_list": "لیست درخواست ها",
|
||||
"details": "جزئیات درخواست",
|
||||
"basic_info": "اطلاعات پایه",
|
||||
"company_info": "اطلاعات شرکت",
|
||||
"user_info": "اطلاعات کاربر",
|
||||
"additional_info": "اطلاعات تکمیلی",
|
||||
"id": "شناسه",
|
||||
"created_at": "تاریخ ایجاد",
|
||||
"updated_at": "تاریخ بروزرسانی",
|
||||
"status": "وضعیت",
|
||||
"reject_reason": "دلیل رد",
|
||||
"company_name": "نام شرکت",
|
||||
"ceo": "مدیرعامل",
|
||||
"email": "ایمیل",
|
||||
"phone": "شماره تماس",
|
||||
"address": "آدرس",
|
||||
"identification_number": "شماره شناسایی",
|
||||
"date_of_establishment": "تاریخ تاسیس",
|
||||
"description": "توضیحات",
|
||||
"company_status": "وضعیت شرکت",
|
||||
"business": "کسب و کار",
|
||||
"invoice_count": "تعداد فاکتور",
|
||||
"website": "وبسایت",
|
||||
"map_location": "موقعیت مکانی",
|
||||
"view_on_map": "مشاهده روی نقشه",
|
||||
"profile_image": "تصویر پروفایل",
|
||||
"cover_image": "تصویر کاور",
|
||||
"view_image": "مشاهده تصویر",
|
||||
"products": "محصولات",
|
||||
"services": "خدمات",
|
||||
"actions": "عملیات",
|
||||
"approve": "تایید",
|
||||
"reject": "رد",
|
||||
"approving": "در حال تایید",
|
||||
"rejecting": "در حال رد",
|
||||
"enter_reject_reason": "دلیل رد درخواست را وارد کنید",
|
||||
"approve_success": "درخواست با موفقیت تایید شد",
|
||||
"reject_success": "درخواست با موفقیت رد شد",
|
||||
"approve_error": "خطا در تایید درخواست",
|
||||
"reject_error": "خطا در رد درخواست",
|
||||
"pending": "در انتظار بررسی",
|
||||
"approved": "تایید شده",
|
||||
"rejected": "رد شده",
|
||||
"industry": "صنعت",
|
||||
"full_name": "نام و نام خانوادگی",
|
||||
"national_code": "کد ملی",
|
||||
"role": "نقش",
|
||||
"customer": "مشتری",
|
||||
"date_receipt": "تاریخ درخواست",
|
||||
"last_date_receipt": "آخرین تاریخ درخواست",
|
||||
"total": "مبلغ کل",
|
||||
"lateFee": "جریمه دیرکرد",
|
||||
"service": "سرویس",
|
||||
"status_paid": "وضعیت پرداخت",
|
||||
"recurring": "تکرار شونده",
|
||||
"maxRecurringCycles": "حداکثر تعداد تکرار",
|
||||
"paid": "پرداخت شده",
|
||||
"recurringPeriod": "دوره تکرار",
|
||||
"user": "کاربر"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,362 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useParams } from 'react-router-dom'
|
||||
import moment from 'moment-jalaali'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { useChangeRequestStatus, useGetRequestDetail } from './hooks/useRequestData'
|
||||
import ModalConfrim from '../../components/ModalConfrim'
|
||||
import { toast } from 'react-toastify'
|
||||
import { ErrorType } from '../../helpers/types'
|
||||
interface Product {
|
||||
id: string
|
||||
title: string
|
||||
imageUrl: string
|
||||
}
|
||||
|
||||
interface Service {
|
||||
id: string
|
||||
title: string
|
||||
imageUrl: string
|
||||
}
|
||||
|
||||
interface Business {
|
||||
id: string
|
||||
name: string
|
||||
slug: string
|
||||
}
|
||||
|
||||
interface Company {
|
||||
id: string
|
||||
name: string
|
||||
chiefExecutiveOfficer: string
|
||||
email: string
|
||||
phone: string
|
||||
identificationNumber: string
|
||||
dateOfEstablishment: string
|
||||
address: string
|
||||
mapAddressLink: string
|
||||
description: string
|
||||
websiteUrl: string
|
||||
profileImageUrl: string
|
||||
coverImageUrl: string
|
||||
status: string
|
||||
industry: string
|
||||
business: string
|
||||
products: Product[]
|
||||
services: Service[]
|
||||
}
|
||||
|
||||
interface Request {
|
||||
id: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
rejectReason: string | null
|
||||
requestStatus: string
|
||||
company: Company
|
||||
business: Business
|
||||
}
|
||||
|
||||
const RequestDetail: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const { id } = useParams()
|
||||
const { data: response, isLoading } = useGetRequestDetail(id || '')
|
||||
const [rejectReason, setRejectReason] = useState('')
|
||||
const [showRejectModal, setShowRejectModal] = useState(false)
|
||||
const { mutate: changeRequestStatus, isPending } = useChangeRequestStatus()
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='mt-5'>
|
||||
<PageLoading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!response?.data?.request) {
|
||||
return <div>Request not found</div>
|
||||
}
|
||||
|
||||
const request: Request = response.data.request
|
||||
|
||||
const handleApprove = () => {
|
||||
if (id) {
|
||||
changeRequestStatus({
|
||||
id,
|
||||
status: 'APPROVED',
|
||||
rejectReason: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleReject = (rejectReason: string) => {
|
||||
if (id) {
|
||||
changeRequestStatus({
|
||||
id,
|
||||
status: 'REJECTED',
|
||||
rejectReason: rejectReason
|
||||
}, {
|
||||
onSuccess: () => {
|
||||
toast.success(t('success'))
|
||||
window.location.reload()
|
||||
},
|
||||
onError: (error: ErrorType) => {
|
||||
toast.error(error.response?.data?.error?.message[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div className='flex items-center gap-4 mb-6'>
|
||||
<h1 className='text-lg font-bold'>{t('request.details')}</h1>
|
||||
</div>
|
||||
|
||||
<div className='bg-white rounded-lg shadow p-6'>
|
||||
<div className='grid grid-cols-2 gap-6'>
|
||||
<div className='space-y-6'>
|
||||
<div className='bg-gray-50 p-4 rounded-lg'>
|
||||
<h2 className='text-base font-semibold mb-4'>{t('request.basic_info')}</h2>
|
||||
<div className='space-y-3'>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.id')}:</span>
|
||||
<span className='text-sm font-medium'>{request.id}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.created_at')}:</span>
|
||||
<span className='text-sm font-medium'>{moment(request.createdAt).format('jYYYY/jMM/jDD HH:mm')}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.updated_at')}:</span>
|
||||
<span className='text-sm font-medium'>{moment(request.updatedAt).format('jYYYY/jMM/jDD HH:mm')}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.status')}:</span>
|
||||
<span className='text-sm font-medium'>{request.requestStatus ? t(`request.${request.requestStatus.toLowerCase()}`) : '-'}</span>
|
||||
</div>
|
||||
{request.rejectReason && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.reject_reason')}:</span>
|
||||
<span className='text-sm font-medium'>{request.rejectReason}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='bg-gray-50 p-4 rounded-lg'>
|
||||
<h2 className='text-base font-semibold mb-4'>{t('request.company_info')}</h2>
|
||||
<div className='space-y-3'>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.company_name')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.name || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.ceo')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.chiefExecutiveOfficer || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.email')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.email || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.phone')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.phone || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.address')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.address || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.identification_number')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.identificationNumber || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.date_of_establishment')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.dateOfEstablishment ? moment(request.company.dateOfEstablishment).format('jYYYY/jMM/jDD') : '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.description')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.description || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.company_status')}:</span>
|
||||
<span className='text-sm font-medium'>{request.company.status ? t(`request.${request.company.status.toLowerCase()}`) : '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.business')}:</span>
|
||||
<span className='text-sm font-medium'>{request.business.name || '-'}</span>
|
||||
</div>
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.invoice_count')}:</span>
|
||||
<span className='text-sm font-medium'>0</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='space-y-6'>
|
||||
<div className='bg-gray-50 p-4 rounded-lg'>
|
||||
<h2 className='text-base font-semibold mb-4'>{t('request.additional_info')}</h2>
|
||||
<div className='space-y-3'>
|
||||
{request.company.websiteUrl && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.website')}:</span>
|
||||
<a
|
||||
href={request.company.websiteUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='text-sm text-blue-600 hover:underline'
|
||||
>
|
||||
{request.company.websiteUrl}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{request.company.mapAddressLink && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.map_location')}:</span>
|
||||
<a
|
||||
href={request.company.mapAddressLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='text-sm text-blue-600 hover:underline'
|
||||
>
|
||||
{t('request.view_on_map')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{request.company.profileImageUrl && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.profile_image')}:</span>
|
||||
<a
|
||||
href={request.company.profileImageUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='text-sm text-blue-600 hover:underline'
|
||||
>
|
||||
{t('request.view_image')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
{request.company.coverImageUrl && (
|
||||
<div className='flex justify-between'>
|
||||
<span className='text-sm text-gray-600'>{t('request.cover_image')}:</span>
|
||||
<a
|
||||
href={request.company.coverImageUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className='text-sm text-blue-600 hover:underline'
|
||||
>
|
||||
{t('request.view_image')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{request.company.products && request.company.products.length > 0 && (
|
||||
<div className='bg-gray-50 p-4 rounded-lg'>
|
||||
<h2 className='text-base font-semibold mb-4'>{t('request.products')}</h2>
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
{request.company.products.map((product: Product) => (
|
||||
<div key={product.id} className='bg-white p-3 rounded-lg shadow-sm'>
|
||||
<div className='aspect-video mb-2 overflow-hidden rounded-lg'>
|
||||
<img
|
||||
src={product.imageUrl}
|
||||
alt={product.title}
|
||||
className='w-full h-full object-cover'
|
||||
/>
|
||||
</div>
|
||||
<h3 className='text-sm font-medium'>{product.title}</h3>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{request.company.services && request.company.services.length > 0 && (
|
||||
<div className='bg-gray-50 p-4 rounded-lg'>
|
||||
<h2 className='text-base font-semibold mb-4'>{t('request.services')}</h2>
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
{request.company.services.map((service: Service) => (
|
||||
<div key={service.id} className='bg-white p-3 rounded-lg shadow-sm'>
|
||||
<div className='aspect-video mb-2 overflow-hidden rounded-lg'>
|
||||
<img
|
||||
src={service.imageUrl}
|
||||
alt={service.title}
|
||||
className='w-full h-full object-cover'
|
||||
/>
|
||||
</div>
|
||||
<h3 className='text-sm font-medium'>{service.title}</h3>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{request.requestStatus === 'PENDING' && (
|
||||
<div className='bg-gray-50 p-4 rounded-lg'>
|
||||
<h2 className='text-base font-semibold mb-4'>{t('request.actions')}</h2>
|
||||
<div className='flex gap-4'>
|
||||
<button
|
||||
onClick={handleApprove}
|
||||
disabled={isPending}
|
||||
className='flex-1 bg-green-500 text-white py-2 px-4 rounded-lg hover:bg-green-600 transition-colors disabled:opacity-50 text-sm'
|
||||
>
|
||||
{isPending ? t('request.approving') : t('request.approve')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowRejectModal(true)}
|
||||
disabled={isPending}
|
||||
className='flex-1 bg-red-500 text-white py-2 px-4 rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50 text-sm'
|
||||
>
|
||||
{isPending ? t('request.rejecting') : t('request.reject')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalConfrim
|
||||
isHasDescription
|
||||
isOpen={showRejectModal}
|
||||
close={() => setShowRejectModal(false)}
|
||||
label={t('request.reject_reason')}
|
||||
onConfrim={(text) => handleReject(text || '')}
|
||||
/>
|
||||
|
||||
{showRejectModal && (
|
||||
<div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
|
||||
<div className='bg-white p-6 rounded-lg w-96'>
|
||||
<h3 className='text-base font-semibold mb-4'>{t('request.reject_reason')}</h3>
|
||||
<textarea
|
||||
value={rejectReason}
|
||||
onChange={(e) => setRejectReason(e.target.value)}
|
||||
className='w-full p-2 border rounded-lg mb-4 text-sm'
|
||||
rows={4}
|
||||
placeholder={t('request.enter_reject_reason')}
|
||||
/>
|
||||
<div className='flex gap-4'>
|
||||
<button
|
||||
onClick={() => setShowRejectModal(false)}
|
||||
className='flex-1 bg-gray-500 text-white py-2 px-4 rounded-lg hover:bg-gray-600 transition-colors text-sm'
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleReject(rejectReason)}
|
||||
disabled={!rejectReason || isPending}
|
||||
className='flex-1 bg-red-500 text-white py-2 px-4 rounded-lg hover:bg-red-600 transition-colors disabled:opacity-50 text-sm'
|
||||
>
|
||||
{isPending ? t('request.rejecting') : t('request.reject')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RequestDetail
|
||||
@@ -0,0 +1,88 @@
|
||||
import { FC, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetRequests } from './hooks/useRequestData'
|
||||
import Pagination from '../../components/Pagination'
|
||||
import Td from '../../components/Td'
|
||||
import PageLoading from '../../components/PageLoading'
|
||||
import { RequestItemType } from './types/Types'
|
||||
import moment from 'moment-jalaali'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Eye } from 'iconsax-react'
|
||||
import { Pages } from '../../config/Pages'
|
||||
|
||||
const RequestsList: FC = () => {
|
||||
const { t } = useTranslation('global')
|
||||
const [page, setPage] = useState(1)
|
||||
const { data, isLoading } = useGetRequests(page)
|
||||
const navigate = useNavigate()
|
||||
|
||||
const handleRowClick = (id: string) => {
|
||||
navigate(Pages.requests.detail + id)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='mt-4'>
|
||||
<div>
|
||||
{t('request.request_list')}
|
||||
</div>
|
||||
|
||||
{
|
||||
isLoading ?
|
||||
<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('request.user')} />
|
||||
<Td text={t('request.date_receipt')} />
|
||||
<Td text={t('request.status')} />
|
||||
<Td text={t('request.business')} />
|
||||
<Td text={t('request.user_info')} />
|
||||
<Td text={t('detail')} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
data?.data?.requests?.map((item: RequestItemType, index: number) => {
|
||||
return (
|
||||
<tr
|
||||
key={item.id}
|
||||
className='tr hover:bg-gray-50'
|
||||
>
|
||||
<Td text={String(index + 1)} />
|
||||
<Td text={`${item.user.firstName} ${item.user.lastName}`} />
|
||||
<Td text={moment(item.createdAt).format('jYYYY/jMM/jDD')} />
|
||||
<Td text={t(`request.${item.requestStatus.toLowerCase()}`)} />
|
||||
<Td text={item.company?.name} />
|
||||
<Td text={item.company.name} />
|
||||
|
||||
<Td text=''>
|
||||
<Eye
|
||||
size={20}
|
||||
color='#888'
|
||||
onClick={() => handleRowClick(item.id)}
|
||||
/>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<Pagination
|
||||
totalPages={data?.data?.pager?.totalPages}
|
||||
currentPage={page}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RequestsList
|
||||
@@ -0,0 +1,47 @@
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import {
|
||||
approveRequest,
|
||||
getRequestDetail,
|
||||
getRequests,
|
||||
rejectRequest,
|
||||
} from "../service/RequestService";
|
||||
|
||||
export const useGetRequests = (page: number = 1) => {
|
||||
return useQuery({
|
||||
queryKey: ["requests", page],
|
||||
queryFn: () => getRequests(page),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGetRequestDetail = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ["request", id],
|
||||
queryFn: () => getRequestDetail(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useApproveRequest = () => {
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => approveRequest(id),
|
||||
});
|
||||
};
|
||||
|
||||
export const useChangeRequestStatus = () => {
|
||||
return useMutation({
|
||||
mutationFn: ({
|
||||
id,
|
||||
status,
|
||||
rejectReason,
|
||||
}: {
|
||||
id: string;
|
||||
status: string;
|
||||
rejectReason: string;
|
||||
}) => {
|
||||
if (status === "APPROVED") {
|
||||
return approveRequest(id);
|
||||
} else {
|
||||
return rejectRequest(id, status, rejectReason);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import axios from "../../../config/axios";
|
||||
|
||||
export const getRequests = async (page: number = 1) => {
|
||||
const { data } = await axios.get(`/companies/requests?page=${page}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getRequestDetail = async (id: string) => {
|
||||
const { data } = await axios.get(`/companies/requests/${id}`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const approveRequest = async (id: string) => {
|
||||
const { data } = await axios.patch(`/companies/requests/${id}/approve`);
|
||||
return data;
|
||||
};
|
||||
|
||||
export const rejectRequest = async (
|
||||
id: string,
|
||||
status: string,
|
||||
rejectReason: string
|
||||
) => {
|
||||
const { data } = await axios.patch(
|
||||
`/companies/requests/${id}/change-status`,
|
||||
{
|
||||
requestStatus: status,
|
||||
rejectReason,
|
||||
}
|
||||
);
|
||||
return data;
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
export type ApiResponse<T> = {
|
||||
statusCode: number;
|
||||
success: boolean;
|
||||
data: T;
|
||||
};
|
||||
|
||||
export type RequestResponse = {
|
||||
request: RequestItemType;
|
||||
};
|
||||
|
||||
export type RequestItemType = {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
rejectReason: string | null;
|
||||
requestStatus: string;
|
||||
company: {
|
||||
id: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
name: string;
|
||||
chiefExecutiveOfficer: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
identificationNumber: string;
|
||||
dateOfEstablishment: string;
|
||||
address: string;
|
||||
mapAddressLink: string;
|
||||
description: string;
|
||||
websiteUrl: string;
|
||||
profileImageUrl: string;
|
||||
coverImageUrl: string;
|
||||
isActive: boolean;
|
||||
status: string;
|
||||
industry: string;
|
||||
business: string;
|
||||
user: string;
|
||||
invoiceCount: number;
|
||||
};
|
||||
business: string;
|
||||
user: {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
};
|
||||
};
|
||||
@@ -17,7 +17,6 @@ import { ErrorType } from '../../helpers/types'
|
||||
import { clx } from '../../helpers/utils'
|
||||
import { Pages } from '../../config/Pages'
|
||||
import { useMultiUpload } from '../service/hooks/useServiceData'
|
||||
import ReferTicket from './components/ReferTicket'
|
||||
|
||||
const TicketDetail: FC = () => {
|
||||
|
||||
@@ -99,22 +98,11 @@ const TicketDetail: FC = () => {
|
||||
<div className='flex xl:flex-row flex-col-reverse gap-6 xl:mt-8 mt-6'>
|
||||
<div className='flex-1 bg-white py-8 xl:px-10 px-4 rounded-3xl'>
|
||||
<div className='gap-6 rowTwoInput'>
|
||||
<Input
|
||||
label={t('ticket.select_your_service')}
|
||||
value={getMessages.data?.data?.ticket?.danakService?.name || '-'}
|
||||
readOnly
|
||||
/>
|
||||
<Input
|
||||
label={t('ticket.user_name')}
|
||||
value={getMessages.data?.data?.ticket?.user?.firstName + ' ' + getMessages.data?.data?.ticket?.user?.lastName}
|
||||
readOnly
|
||||
/>
|
||||
|
||||
<Input
|
||||
label={t('ticket.user_plan')}
|
||||
value={getMessages.data?.data?.supportPlan?.userSupportPlan?.supportPlan?.name}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-8 xl:text-sm text-xs'>
|
||||
@@ -123,7 +111,7 @@ const TicketDetail: FC = () => {
|
||||
|
||||
{
|
||||
getMessages.data?.data?.messages.map((item: TicketMessageType) => {
|
||||
if (item?.author?.roles[0]?.name === 'user') {
|
||||
if (item?.author?.role?.name === 'USER') {
|
||||
return (
|
||||
<div key={item.id} className='mt-6 xl:text-sm text-xs bg-[#F6F7FA] p-6 rounded-3xl rounded-tr-none xl:max-w-[70%] max-w-[90%]'>
|
||||
<div className='leading-7'>
|
||||
@@ -309,10 +297,6 @@ const TicketDetail: FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{
|
||||
<ReferTicket refetch={getMessages.refetch} />
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -54,9 +54,9 @@ export type TicketMessageType = {
|
||||
id: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
roles: {
|
||||
role: {
|
||||
name: string;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
attachments: {
|
||||
attachmentUrl: string;
|
||||
|
||||
@@ -50,6 +50,8 @@ import CreateCompany from '../pages/company/Create'
|
||||
import Industries from '../pages/company/components/Industries'
|
||||
import CompanyList from '../pages/company/List'
|
||||
import UpdateCompany from '../pages/company/Update'
|
||||
import RequestsList from '../pages/requests/List'
|
||||
import RequestDetail from '../pages/requests/Detail'
|
||||
const MainRouter: FC = () => {
|
||||
|
||||
const { hasSubMenu } = useSharedStore()
|
||||
@@ -112,6 +114,8 @@ const MainRouter: FC = () => {
|
||||
<Route path={Pages.company.industries} element={<Industries />} />
|
||||
<Route path={Pages.company.list} element={<CompanyList />} />
|
||||
<Route path={Pages.company.detail + ':id'} element={<UpdateCompany />} />
|
||||
<Route path={Pages.requests.list} element={<RequestsList />} />
|
||||
<Route path={Pages.requests.detail + ':id'} element={<RequestDetail />} />
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,13 +10,14 @@ import { useSharedStore } from './store/sharedStore'
|
||||
import { useGetProfile } from '../pages/profile/hooks/useProfileData'
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react'
|
||||
import { removeRefreshToken, removeToken } from '../config/func'
|
||||
import { clx } from '../helpers/utils'
|
||||
|
||||
const Header: FC = () => {
|
||||
|
||||
const location = useLocation();
|
||||
const [popoverKey, setPopoverKey] = useState(0);
|
||||
const { t } = useTranslation('global')
|
||||
const { setOpenSidebar, openSidebar } = useSharedStore()
|
||||
const { setOpenSidebar, openSidebar, hasSubMenu } = useSharedStore()
|
||||
const { data } = useGetProfile()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -29,8 +30,13 @@ const Header: FC = () => {
|
||||
window.location.href = Pages.auth.login
|
||||
}
|
||||
|
||||
const hasSubMenuOpen = hasSubMenu && (openSidebar || window.innerWidth > 1139)
|
||||
|
||||
return (
|
||||
<div className='fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]'>
|
||||
<div className={clx(
|
||||
'fixed z-10 right-4 left-4 xl:right-[285px] top-4 xl:h-16 h-12 flex items-center px-6 bg-white justify-between rounded-[32px] xl:w-[calc(100%-305px)]',
|
||||
hasSubMenuOpen && '!right-[320px] !w-[calc(100%-340px)]'
|
||||
)}>
|
||||
|
||||
<div className='min-w-[270px] hidden xl:block'>
|
||||
<Input
|
||||
|
||||
Reference in New Issue
Block a user