2024-02-16 11:09:15 +00:00
import { t } from '@lingui/macro' ;
2024-02-20 08:01:06 +00:00
import { Button , Input , Table , Tag , DatePicker , Form , Modal , Select , Space , Checkbox } from 'antd' ;
2024-02-21 11:11:02 +00:00
import { useState , useEffect } from 'react' ;
2024-02-16 11:09:15 +00:00
import { Layout } from 'antd' ;
2024-02-20 08:01:06 +00:00
import {
EditOutlined , DownloadOutlined , CheckCircleOutlined ,
ClockCircleOutlined ,
ArrowLeftOutlined ,
ArrowRightOutlined ,
FullscreenOutlined ,
FullscreenExitOutlined ,
} from '@ant-design/icons' ;
2024-02-16 11:09:15 +00:00
import FileViewer from '@cyntler/react-doc-viewer' ;
2024-02-20 08:01:06 +00:00
import styled from 'styled-components' ;
2024-02-16 11:09:15 +00:00
const { Sider , Content } = Layout ;
2024-02-21 11:11:02 +00:00
import { baseURL } from "request/api" ;
import moment from 'moment' ;
2024-01-31 04:08:20 +00:00
2024-02-16 11:09:15 +00:00
const siderStyle : React.CSSProperties = {
backgroundColor : '#fafafa' ,
padding : 10 ,
width : 200 ,
} ;
2024-02-20 08:01:06 +00:00
const StyledTable = styled ( Table ) `
& . sbt - table - cell {
padding : 4px ! important ;
}
` ;
const StyledEditOutlined = styled ( EditOutlined ) `
& {
color : # 6666 ff ;
margin - left : 8px ;
}
& : hover {
color : # 0000 ff ;
}
` ;
2024-02-16 11:09:15 +00:00
const columns = [
{
title : 'Key' ,
dataIndex : 'key' ,
key : 'key' ,
2024-02-21 11:11:02 +00:00
width : 200 ,
2024-02-16 11:09:15 +00:00
} ,
2024-02-20 08:01:06 +00:00
{
title : 'Accuracy' ,
dataIndex : 'acc' ,
key : 'acc' ,
render : ( text , record ) = > {
return < div > 100 % < / div > ;
} ,
2024-02-21 11:11:02 +00:00
width : 150 ,
2024-02-20 08:01:06 +00:00
} ,
2024-02-16 11:09:15 +00:00
{
title : 'Predicted' ,
2024-02-21 12:01:19 +00:00
dataIndex : 'predicted' ,
key : 'predicted' ,
2024-02-16 11:09:15 +00:00
} ,
{
title : 'Submitted' ,
2024-02-21 12:01:19 +00:00
dataIndex : 'submitted' ,
key : 'submitted' ,
2024-02-16 11:09:15 +00:00
} ,
{
title : 'Revised' ,
2024-02-21 12:01:19 +00:00
dataIndex : 'revised' ,
key : 'revised' ,
2024-02-20 08:01:06 +00:00
render : ( text , record ) = > {
return (
< div style = { {
display : 'flex' ,
lineHeight : '2' ,
} } >
{ text }
< / div >
)
} ,
2024-02-21 12:01:19 +00:00
} ,
{
title : 'Action' ,
key : 'operation' ,
fixed : 'right' ,
width : 100 ,
render : ( ) = > < a > < StyledEditOutlined / > < / a > ,
2024-02-16 11:09:15 +00:00
} ,
] ;
const FileCard = ( { file , isSelected , onClick } ) = > {
2024-02-21 11:11:02 +00:00
const fileName = file [ "File Name" ] ;
2024-02-16 11:09:15 +00:00
return (
< div style = { {
border : '1px solid #ccc' ,
width : '200px' ,
2024-02-19 04:02:21 +00:00
backgroundColor : isSelected ? '#d4ecff' : '#fff' ,
2024-02-16 11:09:15 +00:00
padding : '4px 8px' ,
marginRight : '4px' ,
2024-02-20 08:01:06 +00:00
marginTop : '2px' ,
position : 'relative' ,
height : '100px' ,
2024-02-16 11:09:15 +00:00
} } onClick = { onClick } >
< div >
< span style = { {
fontSize : '12px' ,
color : '#333' ,
fontWeight : 'bold' ,
padding : '4px 8px' ,
2024-02-20 08:01:06 +00:00
cursor : 'default' ,
2024-02-21 11:11:02 +00:00
} } > { file [ "Doc Type" ] . toUpperCase ( ) } < / span >
2024-02-16 11:09:15 +00:00
< span style = { {
fontSize : '12px' ,
color : '#aaa' ,
fontWeight : 'bold' ,
padding : '4px 8px' ,
2024-02-20 08:01:06 +00:00
cursor : 'default' ,
2024-02-21 11:11:02 +00:00
maxWidth : '50px' ,
overflow : 'hidden' ,
textOverflow : 'ellipsis' ,
} } >
2024-02-21 12:01:19 +00:00
{ fileName ? fileName . substring ( 0 , 25 ) . replace ( "temp_" , "" ) : fileName }
2024-02-21 11:11:02 +00:00
< / span >
2024-02-16 11:09:15 +00:00
< / div >
2024-02-20 08:01:06 +00:00
< div style = { {
padding : '4px' ,
position : 'absolute' ,
bottom : 2 ,
right : 2 ,
} } >
< Button style = { {
margin : '4px 2px' ,
} } >
Review
< / Button >
< Button style = { {
margin : '4px 2px' ,
2024-02-21 12:01:19 +00:00
} } onClick = { ( ) = > {
const downloadUrl = file [ "File URL" ] ;
window . open ( downloadUrl , '_blank' ) ;
2024-02-20 08:01:06 +00:00
} } >
< DownloadOutlined / >
< / Button >
< / div >
2024-02-16 11:09:15 +00:00
< / div >
) ;
} ;
2024-02-21 12:01:19 +00:00
const fetchAllRequests = async ( filterDateRange , filterSubsidiaries , filterReviewState , filterIncludeTests , page = 1 , page_size = 20 ) = > {
const startDate = ( filterDateRange && filterDateRange [ 0 ] ) ? filterDateRange [ 0 ] . format ( 'YYYY-MM-DD' ) : '' ;
const endDate = ( filterDateRange && filterDateRange [ 1 ] ) ? filterDateRange [ 1 ] . format ( 'YYYY-MM-DD' ) : '' ;
2024-02-21 11:11:02 +00:00
let filterStr = "" ;
filterStr += ` page= ${ page } &page_size= ${ page_size } & ` ;
if ( filterSubsidiaries ) {
filterStr += ` subsidiary= ${ filterSubsidiaries } & ` ;
}
if ( filterReviewState ) {
filterStr += ` is_reviewed= ${ filterReviewState } & ` ;
}
if ( filterIncludeTests ) {
filterStr += ` includes_tests= ${ filterIncludeTests } & ` ;
}
if ( startDate && endDate ) {
filterStr += ` start_date= ${ startDate } &end_date= ${ endDate } & ` ;
}
const token = localStorage . getItem ( 'sbt-token' ) || '' ;
const data = await fetch ( ` ${ baseURL } /ctel/request_list/? ${ filterStr } ` , {
method : 'GET' ,
headers : {
"Authorization" : ` ${ JSON . parse ( token ) } `
}
} )
. then ( async ( res ) = > {
const data = await res . json ( ) ;
return data ;
} ) ;
return data ;
} ;
const fetchRequest = async ( id ) = > {
const token = localStorage . getItem ( 'sbt-token' ) || '' ;
const response = await fetch ( ` ${ baseURL } /ctel/request/ ${ id } / ` , {
method : 'GET' ,
headers : {
"Authorization" : ` ${ JSON . parse ( token ) } `
}
} ) ;
return await ( await response . json ( ) ) . subscription_requests [ 0 ] ;
} ;
2024-02-20 08:01:06 +00:00
const ReviewPage = ( ) = > {
const [ fullscreen , setFullscreen ] = useState ( false ) ;
const [ isModalOpen , setIsModalOpen ] = useState ( false ) ;
2024-02-16 11:09:15 +00:00
const [ selectedFileId , setSelectedFileId ] = useState ( 0 ) ;
const selectFileByIndex = ( index ) = > {
setSelectedFileId ( index ) ;
} ;
2024-02-21 11:11:02 +00:00
// Default date range: 1 month ago to today
const [ filterDateRange , setFilterDateRange ] = useState ( [
2024-02-21 12:01:19 +00:00
moment ( ) . subtract ( 1 , 'month' ) ,
2024-02-21 11:11:02 +00:00
moment ( ) ,
] ) ;
const [ filterSubsidiaries , setFilterSubsidiaries ] = useState ( 'ALL' ) ;
const [ filterReviewState , setFilterReviewState ] = useState ( 'all' ) ;
const [ filterIncludeTests , setFilterIncludesTests ] = useState ( 'true' ) ;
const [ requests , setRequests ] = useState ( [ ] ) ;
const [ currentRequest , setCurrentRequest ] = useState ( null ) ;
const [ currentRequestIndex , setCurrentRequestIndex ] = useState ( 1 ) ;
const [ hasNextRequest , setHasNextRequest ] = useState ( true ) ;
const [ totalPages , setTotalPages ] = useState ( 0 ) ;
2024-02-21 12:01:19 +00:00
// purchase_date: "2024-01-20",
// retailername: "Test Retailer",
// sold_to_party: "Test Party",
const dataSource = [
// {
// key: "imei_number",
// predicted: "352271450941944",
// submitted: "352271450941944",
// revised: "352271450941944",
// },
] ;
const predicted = ( currentRequest && currentRequest [ "Reviewed Result" ] ) ? currentRequest [ "Reviewed Result" ] : { } ;
const submitted = ( currentRequest && currentRequest [ "Feedback Result" ] ) ? currentRequest [ "Feedback Result" ] : { } ;
const revised = ( currentRequest && currentRequest [ "Reviewed Result" ] ) ? currentRequest [ "Reviewed Result" ] : { } ;
const keys = Object . keys ( predicted ) ;
for ( let i = 0 ; i < keys . length ; i ++ ) {
let instance = { } ;
instance [ "key" ] = keys [ i ] ;
instance [ "predicted" ] = predicted [ keys [ i ] ] ;
instance [ "submitted" ] = submitted [ keys [ i ] ] ;
instance [ "revised" ] = revised [ keys [ i ] ] ;
dataSource . push ( instance ) ;
}
2024-02-21 11:11:02 +00:00
const gotoNextRequest = ( ) = > {
const nextRequestIndex = currentRequestIndex + 1 ;
setCurrentRequestIndex ( nextRequestIndex ) ;
fetchAllRequests ( filterDateRange , filterSubsidiaries , filterReviewState , filterIncludeTests , nextRequestIndex , 2 ) . then ( ( data ) = > {
setRequests ( data ? . subscription_requests ) ;
setHasNextRequest ( data ? . subscription_requests . length > 1 ) ;
setTotalPages ( data ? . page ? . total_pages ) ;
const requestData = fetchRequest ( data ? . subscription_requests [ 0 ] . RequestID ) ;
requestData . then ( async ( data ) = > {
console . log ( data )
if ( data ) setCurrentRequest ( data ) ;
} ) ;
} ) ;
} ;
const gotoPreviousRequest = ( ) = > {
if ( currentRequestIndex === 1 ) {
return ;
}
const previousRequestIndex = currentRequestIndex - 1 ;
setCurrentRequestIndex ( previousRequestIndex ) ;
fetchAllRequests ( filterDateRange , filterSubsidiaries , filterReviewState , filterIncludeTests , previousRequestIndex , 2 ) . then ( ( data ) = > {
setRequests ( data ? . subscription_requests ) ;
setHasNextRequest ( data ? . subscription_requests . length > 1 ) ;
setTotalPages ( data ? . page ? . total_pages ) ;
const requestData = fetchRequest ( data ? . subscription_requests [ 0 ] . RequestID ) ;
requestData . then ( async ( data ) = > {
console . log ( data )
if ( data ) setCurrentRequest ( data ) ;
} ) ;
} ) ;
} ;
const reloadFilters = ( ) = > {
setCurrentRequestIndex ( 1 ) ;
fetchAllRequests ( filterDateRange , filterSubsidiaries , filterReviewState , filterIncludeTests , currentRequestIndex , 2 ) . then ( ( data ) = > {
setTotalPages ( data ? . page ? . total_pages ) ;
setRequests ( data ? . subscription_requests ) ;
setHasNextRequest ( data ? . subscription_requests . length > 1 ) ;
const firstRequest = fetchRequest ( data ? . subscription_requests [ 0 ] . RequestID ) ;
firstRequest . then ( async ( data ) = > {
console . log ( firstRequest )
if ( data ) setCurrentRequest ( data ) ;
} ) ;
} ) ;
} ;
useEffect ( ( ) = > {
setCurrentRequestIndex ( 1 ) ;
fetchAllRequests ( filterDateRange , filterSubsidiaries , filterReviewState , filterIncludeTests , currentRequestIndex , 2 ) . then ( ( data ) = > {
setTotalPages ( data ? . page ? . total_pages ) ;
setRequests ( data ? . subscription_requests ) ;
setHasNextRequest ( data ? . subscription_requests . length > 1 ) ;
const firstRequest = fetchRequest ( data ? . subscription_requests [ 0 ] . RequestID ) ;
firstRequest . then ( async ( data ) = > {
console . log ( firstRequest )
if ( data ) setCurrentRequest ( data ) ;
} ) ;
} ) ;
} , [ ] ) ;
const fileURL = currentRequest ? baseURL + currentRequest [ "Files" ] [ selectedFileId ] [ "File URL" ] . replace ( "http://be-ctel-sbt:9000/api" , "" ) : "dummy.pdf" ;
2024-02-16 11:09:15 +00:00
return (
2024-02-20 08:01:06 +00:00
< div style = { fullscreen ? {
position : 'absolute' ,
top : 0 ,
left : 0 ,
width : '100%' ,
height : '100%' ,
backgroundColor : '#fff' ,
zIndex : 1000 ,
} : {
height : '100%' ,
} } >
2024-02-21 12:01:19 +00:00
< div >
< Button onClick = { ( ) = > {
setFullscreen ( ! fullscreen ) ;
} } >
{ fullscreen ? < FullscreenExitOutlined / > : < FullscreenOutlined / > }
{ fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen' }
< / Button >
& nbsp ; & nbsp ; & nbsp ; < b > Request ID : < / b > { currentRequest ? . RequestID }
< / div >
2024-02-16 11:09:15 +00:00
< Layout style = { {
2024-02-20 08:01:06 +00:00
overflow : 'auto' ,
2024-02-16 11:09:15 +00:00
width : '100%' ,
2024-02-20 08:01:06 +00:00
height : '100%' ,
2024-02-16 11:09:15 +00:00
maxWidth : '100%' ,
2024-02-21 11:11:02 +00:00
minHeight : '70%' ,
maxHeight : '70%' ,
2024-02-16 11:09:15 +00:00
display : 'flex' ,
padding : '8px' ,
} } >
2024-02-20 08:01:06 +00:00
< Content style = { {
textAlign : 'center' ,
color : '#fff' ,
backgroundColor : '#efefef' ,
height : '100%' ,
display : 'flex' ,
flexDirection : 'row' ,
} } >
< div
style = { {
width : "200px" ,
display : "flex" ,
flexDirection : "column" ,
flexGrow : 0 ,
} } >
< h2
style = { {
color : "#333" ,
padding : 10 ,
2024-02-21 11:11:02 +00:00
fontWeight : 'bold'
2024-02-20 08:01:06 +00:00
} }
2024-02-21 11:11:02 +00:00
> Files ( { currentRequest ? . Files ? . length } ) < / h2 >
{ currentRequest ? . Files . map ( ( file , index ) = > (
2024-02-20 08:01:06 +00:00
< FileCard key = { index } file = { file } isSelected = { index === selectedFileId } onClick = {
( ) = > {
setSelectedFileId ( index ) ;
}
} / >
) ) }
< / div >
< div style = { {
border : "1px solid #ccc" ,
2024-02-19 04:02:21 +00:00
flexGrow : 1 ,
2024-02-20 08:01:06 +00:00
height : '100%' ,
2024-02-19 04:02:21 +00:00
} } >
2024-02-20 08:01:06 +00:00
< FileViewer documents = {
[
2024-02-21 11:11:02 +00:00
{ uri : fileURL }
2024-02-20 08:01:06 +00:00
]
} config = { {
header : {
disableHeader : true ,
disableFileName : true ,
retainURLParams : true ,
} ,
csvDelimiter : "," , // "," as default,
pdfVerticalScrollByDefault : true , // false as default
} } / >
< / div >
< / Content >
2024-02-21 11:11:02 +00:00
< Sider width = "400px" style = { siderStyle } >
2024-02-20 08:01:06 +00:00
< Space.Compact style = { { width : '100%' , marginBottom : 16 } } >
2024-02-21 11:11:02 +00:00
< Input value = {
` Sub: ${ filterSubsidiaries } , Date: ${ filterDateRange [ 0 ] ? ( filterDateRange [ 0 ] ? . format ( 'YYYY-MM-DD' ) + " to " + filterDateRange [ 1 ] . format ( 'YYYY-MM-DD' ) ) : "All" } , Reviewed: ${ filterReviewState } , Tests: ${ filterIncludeTests } `
} readOnly / >
2024-02-20 08:01:06 +00:00
< Button type = "primary" size = "large"
onClick = { ( ) = > {
setIsModalOpen ( true ) ;
} }
>
Filters
< / Button >
< / Space.Compact >
< div style = { { display : "flex" , justifyContent : "space-between" , marginBottom : 8 } } >
< div >
2024-02-21 12:01:19 +00:00
< Button type = "default" style = { { height : 38 } }
2024-02-21 11:11:02 +00:00
disabled = { currentRequestIndex === 1 }
onClick = { ( ) = > {
gotoPreviousRequest ( ) ;
} }
>
2024-02-20 08:01:06 +00:00
< ArrowLeftOutlined / >
Previous
< / Button >
2024-02-21 12:01:19 +00:00
< Button type = "default" style = { { height : 38 } }
2024-02-21 11:11:02 +00:00
disabled = { ! hasNextRequest }
onClick = { ( ) = > {
if ( ! hasNextRequest ) {
return ;
}
gotoNextRequest ( ) ;
} }
>
2024-02-20 08:01:06 +00:00
Next
< ArrowRightOutlined / >
< / Button >
2024-02-21 12:01:19 +00:00
< Input size = 'middle' addonBefore = "To" style = { { marginBottom : "4px" , marginLeft : "4px" , width : 180 } } defaultValue = { currentRequestIndex } addonAfter = {
< Button type = "default" >
Go to
< / Button >
} / >
2024-02-16 11:09:15 +00:00
< / div >
2024-02-20 08:01:06 +00:00
< / div >
2024-02-21 11:11:02 +00:00
< h2 style = { { margin : "20px 0 10px 0" } } > { totalPages ? ( "Request: " + currentRequestIndex + "/" + totalPages ) : "No Request. Adjust your search criteria to see more results." } < / h2 >
< Input size = 'small' addonBefore = "Request ID" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest . RequestID : "" } / >
2024-02-21 12:01:19 +00:00
< Input size = 'small' addonBefore = "Redemption" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? . RedemptionID ? currentRequest . RedemptionID : "" } / >
2024-02-21 11:11:02 +00:00
< Input size = 'small' addonBefore = "Uploaded date" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest . created_at : "" } / >
< Input size = 'small' addonBefore = "Request time" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest [ "Client Request Time (ms)" ] : "" } / >
< Input size = 'small' addonBefore = "Processing time" style = { { marginBottom : "4px" } } readOnly value = { currentRequest ? currentRequest [ "Server Processing Time (ms)" ] : "" } / >
2024-02-20 08:01:06 +00:00
< div style = { { marginBottom : "8px" , marginTop : "8px" , display : "flex" } } >
< Button type = "primary" > Mark Not - reviewed < / Button >
{ /* <Tag icon={<ClockCircleOutlined / > } color = "warning" style = { { padding : "4px 16px" , marginLeft : 8 } } >
Not Reviewed
< / Tag > * / }
< Tag icon = { < CheckCircleOutlined / > } color = "success" style = { { padding : "4px 16px" , marginLeft : 8 } } >
Reviewed
< / Tag >
< / div >
< / Sider >
< / Layout >
< Modal
title = { t ` Report Filters ` }
open = { isModalOpen }
width = { 700 }
onOk = {
( ) = > {
setIsModalOpen ( false ) ;
2024-02-21 11:11:02 +00:00
reloadFilters ( ) ;
2024-02-20 08:01:06 +00:00
}
}
onCancel = {
( ) = > {
setIsModalOpen ( false ) ;
}
}
>
< Form
style = { {
marginTop : 30 ,
} }
>
< Form.Item
name = 'dateRange'
label = { t ` Date (GMT+8) ` }
rules = { [
{
required : true ,
message : 'Please select a date range' ,
} ,
] }
style = { {
marginBottom : 10 ,
} }
>
< DatePicker.RangePicker
value = { filterDateRange }
onChange = { ( value ) = > {
setFilterDateRange ( value ) ;
} }
style = { { width : 200 } }
/ >
< / Form.Item >
2024-02-21 11:11:02 +00:00
< Form.Item
name = 'subsidiary'
label = { t ` Subsidiary ` }
rules = { [
{
required : true ,
message : 'Please select a subsidiary' ,
} ,
] }
style = { {
marginBottom : 10 ,
} }
>
< Select
placeholder = 'Select a subsidiary'
style = { { width : 200 } }
options = { [
{ value : 'ALL' , label : 'ALL' } ,
{ value : 'SEAU' , label : 'SEAU' } ,
{ value : 'SESP' , label : 'SESP' } ,
{ value : 'SME' , label : 'SME' } ,
{ value : 'SEPCO' , label : 'SEPCO' } ,
{ value : 'TSE' , label : 'TSE' } ,
{ value : 'SEIN' , label : 'SEIN' } ,
] }
value = { filterSubsidiaries }
defaultValue = { filterSubsidiaries }
onChange = { setFilterSubsidiaries }
/ >
< / Form.Item >
< div style = { { marginTop : 10 , display : 'flex' , marginLeft : 0 , padding : 0 } } >
2024-02-20 08:01:06 +00:00
< Form.Item
2024-02-21 11:11:02 +00:00
name = 'reviewed'
label = { t ` Reviewed ` }
2024-02-20 08:01:06 +00:00
rules = { [
{
required : true ,
2024-02-21 11:11:02 +00:00
message : 'Please select review status' ,
2024-02-20 08:01:06 +00:00
} ,
] }
>
< Select
style = { { width : 200 } }
options = { [
2024-02-21 11:11:02 +00:00
{ label : 'All' , value : 'all' } ,
{ label : 'Reviewed' , value : 'reviewed' } ,
{ label : 'Not Reviewed' , value : 'not_reviewed' } ,
2024-02-20 08:01:06 +00:00
] }
2024-02-21 11:11:02 +00:00
value = { filterReviewState }
defaultValue = { filterReviewState }
onChange = { setFilterReviewState }
2024-02-20 08:01:06 +00:00
/ >
< / Form.Item >
< Form.Item
2024-02-21 11:11:02 +00:00
name = 'is_test'
label = { t ` Is Test ` }
2024-02-20 08:01:06 +00:00
rules = { [
{
required : true ,
2024-02-21 11:11:02 +00:00
message : 'Please select test status' ,
2024-02-20 08:01:06 +00:00
} ,
] }
style = { { marginLeft : 16 } }
>
< Select
style = { { width : 200 } }
options = { [
2024-02-21 11:11:02 +00:00
{ label : 'Include tests' , value : 'true' } ,
{ label : 'Exclude tests' , value : 'false' } ,
2024-02-20 08:01:06 +00:00
] }
2024-02-21 11:11:02 +00:00
value = { filterIncludeTests }
defaultValue = { filterIncludeTests }
onChange = { setFilterIncludesTests }
2024-02-20 08:01:06 +00:00
/ >
< / Form.Item >
< / div >
< / Form >
< / Modal >
2024-02-21 11:11:02 +00:00
< div
2024-02-20 08:01:06 +00:00
style = { {
2024-02-21 11:11:02 +00:00
height : '25%' ,
2024-02-20 08:01:06 +00:00
overflowY : 'auto' ,
} }
>
2024-02-21 11:11:02 +00:00
< StyledTable dataSource = { dataSource } columns = { columns }
2024-02-20 08:01:06 +00:00
/ >
< / div >
< / div >
2024-02-16 11:09:15 +00:00
) ;
} ;
2024-02-20 08:01:06 +00:00
export default ReviewPage ;