Merge branch 'main' of https://code.sdsdev.co.kr/SDSRV-IDP/sbt-idp into dev/all_rq_for_acc

This commit is contained in:
dx-tan 2024-02-23 12:52:23 +07:00
commit c605b51863
15 changed files with 926 additions and 160 deletions

View File

@ -16,12 +16,14 @@ import json
from ..exception.exceptions import InvalidException, RequiredFieldException, NotFoundException from ..exception.exceptions import InvalidException, RequiredFieldException, NotFoundException
from ..models import SubscriptionRequest, Report, ReportFile, SubscriptionRequestFile from ..models import SubscriptionRequest, Report, ReportFile, SubscriptionRequestFile
from ..utils.accuracy import shadow_report, MonthReportAccumulate, first_of_list, extract_report_detail_list, IterAvg from ..utils.accuracy import shadow_report, MonthReportAccumulate, first_of_list, extract_report_detail_list, IterAvg
from ..utils.file import download_from_S3, convert_date_string, build_media_url_v2, build_url, dict2xlsx, save_report_to_S3 from ..utils.file import download_from_S3, dict2xlsx, save_report_to_S3, build_S3_url
from ..utils.redis import RedisUtils from ..utils.redis import RedisUtils
from ..utils.process import string_to_boolean from ..utils.process import string_to_boolean
from ..request.ReportCreationSerializer import ReportCreationSerializer from ..request.ReportCreationSerializer import ReportCreationSerializer
from ..utils.subsidiary import map_subsidiary_long_to_short, map_subsidiary_short_to_long from ..utils.subsidiary import map_subsidiary_long_to_short, map_subsidiary_short_to_long
from ..utils.report import aggregate_overview from ..utils.report import aggregate_overview
from fwd_api.utils.accuracy import predict_result_to_ready
import copy
redis_client = RedisUtils() redis_client = RedisUtils()
@ -112,6 +114,7 @@ class AccuracyViewSet(viewsets.ViewSet):
try: try:
start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d') # We care only about day precision only start_date = timezone.datetime.strptime(start_date_str, '%Y-%m-%d') # We care only about day precision only
end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d') end_date = timezone.datetime.strptime(end_date_str, '%Y-%m-%d')
end_date = end_date + timezone.timedelta(days=1)
# Round: # Round:
# end_date_str to the beginning of the next day # end_date_str to the beginning of the next day
# start_date_str to the start of the date # start_date_str to the start of the date
@ -119,7 +122,7 @@ class AccuracyViewSet(viewsets.ViewSet):
end_date = timezone.make_aware(end_date) end_date = timezone.make_aware(end_date)
start_date_str = start_date.strftime('%Y-%m-%dT%H:%M:%S%z') # inside logic will include second precision with timezone for calculation start_date_str = start_date.strftime('%Y-%m-%dT%H:%M:%S%z') # inside logic will include second precision with timezone for calculation
end_date_str = (end_date + timezone.timedelta(days=1)).strftime('%Y-%m-%dT%H:%M:%S%z') end_date_str = end_date.strftime('%Y-%m-%dT%H:%M:%S%z')
base_query &= Q(created_at__range=(start_date, end_date)) base_query &= Q(created_at__range=(start_date, end_date))
except Exception as e: except Exception as e:
raise InvalidException(excArgs="Date format") raise InvalidException(excArgs="Date format")
@ -128,15 +131,13 @@ class AccuracyViewSet(viewsets.ViewSet):
base_query &= Q(request_id=request_id) base_query &= Q(request_id=request_id)
if redemption_id: if redemption_id:
base_query &= Q(redemption_id=redemption_id) base_query &= Q(redemption_id=redemption_id)
base_query &= Q(is_test_request=False)
if isinstance(include_test, str): if isinstance(include_test, str):
include_test = True if include_test=="true" else False include_test = True if include_test=="true" else False
if include_test: if not include_test:
# base_query = ~base_query base_query &= Q(is_test_request=False)
base_query.children = base_query.children[:-1]
elif isinstance(include_test, bool): elif isinstance(include_test, bool):
if include_test: if not include_test:
base_query = ~base_query base_query &= Q(is_test_request=False)
if isinstance(is_reviewed, str): if isinstance(is_reviewed, str):
if is_reviewed == "reviewed": if is_reviewed == "reviewed":
base_query &= Q(is_reviewed=True) base_query &= Q(is_reviewed=True)
@ -341,7 +342,7 @@ class AccuracyViewSet(viewsets.ViewSet):
location=OpenApiParameter.QUERY, location=OpenApiParameter.QUERY,
description='Start date (YYYY-mm-DDTHH:MM:SSZ)', description='Start date (YYYY-mm-DDTHH:MM:SSZ)',
type=OpenApiTypes.DATE, type=OpenApiTypes.DATE,
default='2023-01-02T00:00:00+0700', default='2024-01-02T00:00:00+0700',
), ),
OpenApiParameter( OpenApiParameter(
name='end_date', name='end_date',
@ -628,8 +629,6 @@ class AccuracyViewSet(viewsets.ViewSet):
return response return response
return JsonResponse({'error': 'Invalid request method.'}, status=405) return JsonResponse({'error': 'Invalid request method.'}, status=405)
class RequestViewSet(viewsets.ViewSet):
lookup_field = "username"
@extend_schema( @extend_schema(
request={ request={
@ -644,7 +643,7 @@ class RequestViewSet(viewsets.ViewSet):
}, },
}, },
responses=None, responses=None,
tags=['Request'] tags=['Accuracy']
) )
@action(detail=False, url_path=r"request/(?P<request_id>[\w\-]+)", methods=["GET", "POST"]) @action(detail=False, url_path=r"request/(?P<request_id>[\w\-]+)", methods=["GET", "POST"])
def get_subscription_request(self, request, request_id=None): def get_subscription_request(self, request, request_id=None):
@ -658,6 +657,14 @@ class RequestViewSet(viewsets.ViewSet):
subscription_request = subscription_request.first() subscription_request = subscription_request.first()
sample_result = {
"request_id": subscription_request.request_id,
"retailername": None,
"sold_to_party": None,
"purchase_date": None,
"imei_number": []
}
data = [] data = []
files = [] files = []
@ -668,26 +675,48 @@ class RequestViewSet(viewsets.ViewSet):
user_id = sub.user.id user_id = sub.user.id
sync_id = sub.user.sync_id sync_id = sub.user.sync_id
sub_id = sub.id sub_id = sub.id
reviewed_result = subscription_request_file.reviewed_result
feedback_result = subscription_request_file.feedback_result
predicted_result = subscription_request_file.predict_result
if not reviewed_result:
reviewed_result = copy.deepcopy(sample_result)
if not feedback_result:
feedback_result = copy.deepcopy(sample_result)
if not predicted_result:
predicted_result = copy.deepcopy(sample_result)
files.append({ files.append({
'File Name': subscription_request_file.file_name, 'File Name': subscription_request_file.file_name,
'File Path': subscription_request_file.file_path, 'File Path': subscription_request_file.file_path,
'File Category': subscription_request_file.file_category, 'File Category': subscription_request_file.file_category,
'File URL': build_media_url_v2(subscription_request_file.file_name.split('.')[0], user_id, sub_id, sync_id), 'File URL': build_S3_url("sbt_invoice/" + subscription_request.request_id + "/" + subscription_request_file.file_name, 600),
'Origin_Name': subscription_request_file.origin_name, 'Original Name': subscription_request_file.origin_name,
'Is Bad Image Quality': subscription_request_file.is_bad_image_quality, 'Is Bad Image Quality': subscription_request_file.is_bad_image_quality,
'Doc Type': subscription_request_file.doc_type, 'Doc Type': subscription_request_file.doc_type,
'Processing Time (ms)': subscription_request_file.processing_time, 'Processing Time (ms)': subscription_request_file.processing_time,
'Reason': subscription_request_file.reason, 'Reason': subscription_request_file.reason,
'Counter Measures': subscription_request_file.counter_measures, 'Counter Measures': subscription_request_file.counter_measures,
'Predicted Result': subscription_request_file.predict_result, 'Predicted Result': predicted_result,
'Feedback Result': subscription_request_file.feedback_result, 'Feedback Result': feedback_result,
'Reviewed Result': subscription_request_file.reviewed_result, 'Reviewed Result': reviewed_result,
'Feedback Accuracy': subscription_request_file.feedback_accuracy, 'Feedback Accuracy': subscription_request_file.feedback_accuracy,
'Reviewed Accuracy': subscription_request_file.reviewed_accuracy, 'Reviewed Accuracy': subscription_request_file.reviewed_accuracy,
'Created At': subscription_request_file.created_at.isoformat(), 'Created At': subscription_request_file.created_at.isoformat(),
'Updated At': subscription_request_file.updated_at.isoformat() 'Updated At': subscription_request_file.updated_at.isoformat()
}) })
reviewed_result = subscription_request.reviewed_result
feedback_result = subscription_request.feedback_result
predicted_result = predict_result_to_ready(subscription_request.predict_result)
if not reviewed_result:
reviewed_result = copy.deepcopy(sample_result)
if not feedback_result:
feedback_result = copy.deepcopy(sample_result)
if not predicted_result:
predicted_result = copy.deepcopy(sample_result)
data.append({ data.append({
'Document Type': subscription_request.doc_type, 'Document Type': subscription_request.doc_type,
'RequestID': subscription_request.request_id, 'RequestID': subscription_request.request_id,
@ -696,9 +725,9 @@ class RequestViewSet(viewsets.ViewSet):
'Provider Code': subscription_request.provider_code, 'Provider Code': subscription_request.provider_code,
'Status': subscription_request.status, 'Status': subscription_request.status,
'Files': files, 'Files': files,
'Reviewed Result': subscription_request.reviewed_result, 'Reviewed Result': reviewed_result,
'Feedback Result': subscription_request.feedback_result, 'Feedback Result': feedback_result,
'Predicted Result': subscription_request.predict_result, 'Predicted Result': predicted_result,
'Is Test Request': subscription_request.is_test_request, 'Is Test Request': subscription_request.is_test_request,
'Client Request Time (ms)': subscription_request.client_request_time, 'Client Request Time (ms)': subscription_request.client_request_time,
'Server Processing Time (ms)': subscription_request.preprocessing_time + subscription_request.ai_inference_time, 'Server Processing Time (ms)': subscription_request.preprocessing_time + subscription_request.ai_inference_time,
@ -729,19 +758,29 @@ class RequestViewSet(viewsets.ViewSet):
subscription_request_files = SubscriptionRequestFile.objects.filter(request=subscription_request.id) subscription_request_files = SubscriptionRequestFile.objects.filter(request=subscription_request.id)
reviewed_result = json.loads(data["reviewed_result"]) if "reviewed_result" not in data:
raise InvalidException(excArgs=f'reviewed_result')
reviewed_result = data["reviewed_result"]
for field in ['retailername', 'sold_to_party', 'purchase_date', 'imei_number']:
if not field in reviewed_result.keys():
raise RequiredFieldException(excArgs=f'reviewed_result.{field}')
reviewed_result['request_id'] = request_id
for subscription_request_file in subscription_request_files: for subscription_request_file in subscription_request_files:
if subscription_request_file.doc_type == 'invoice': if subscription_request_file.doc_type == 'invoice':
subscription_request_file.reviewed_result = reviewed_result subscription_request_file.reviewed_result = reviewed_result
subscription_request_file.reviewed_result['imei_number'] = [] subscription_request_file.reviewed_result['imei_number'] = []
elif subscription_request_file.doc_type == 'imei': elif subscription_request_file.doc_type == 'imei':
subscription_request_file.reviewed_result = {"retailername": None, "sold_to_party": None, "purchase_date": [], "imei_number": [reviewed_result["imei_number"][subscription_request_file.index_in_request]]} subscription_request_file.reviewed_result = {
"retailername": None,
"sold_to_party": None,
"purchase_date": [],
"imei_number": []}
if len(reviewed_result["imei_number"]) - 1 >= subscription_request_file.index_in_request:
subscription_request_file.reviewed_result["imei_number"] = reviewed_result["imei_number"][subscription_request_file.index_in_request]
subscription_request_file.save() subscription_request_file.save()
for field in ['retailername', 'sold_to_party', 'purchase_date', 'imei_number']:
if not field in reviewed_result.keys():
raise RequiredFieldException(excArgs=f'reviewed_result.{field}')
subscription_request.reviewed_result = reviewed_result subscription_request.reviewed_result = reviewed_result
subscription_request.reviewed_result['request_id'] = request_id subscription_request.reviewed_result['request_id'] = request_id
subscription_request.is_reviewed = True subscription_request.is_reviewed = True
@ -750,3 +789,50 @@ class RequestViewSet(viewsets.ViewSet):
return JsonResponse({'message': 'success.'}, status=200) return JsonResponse({'message': 'success.'}, status=200)
else: else:
return JsonResponse({'error': 'Invalid request method.'}, status=405) return JsonResponse({'error': 'Invalid request method.'}, status=405)
@extend_schema(
request={
'multipart/form-data': {
'type': 'object',
'properties': {
'reason': {
'type': 'string',
'default': '''"Sample reason"''',
},
},
},
},
responses=None,
tags=['Accuracy']
)
@action(detail=False, url_path=r"request_image/(?P<request_id>[\w\-]+)/(?P<request_image_id>[\w\-]+)", methods=["POST"])
def request_image(self, request, request_id=None, request_image_id=None):
if request.method == 'POST':
data = request.data
base_query = Q(request_id=request_id)
subscription_request = SubscriptionRequest.objects.filter(base_query)
if subscription_request.count() == 0:
raise NotFoundException(excArgs=request_id)
subscription_request = subscription_request.first()
subscription_request_files = SubscriptionRequestFile.objects.filter(request=subscription_request.id)
if "reason" not in data:
raise InvalidException(excArgs=f'reason')
reason = data["reason"]
is_available = False
for subscription_request_file in subscription_request_files:
if subscription_request_file.file_name.split(".")[0] == request_image_id:
subscription_request_file.reason = reason
subscription_request_file.save()
is_available = True
if not is_available:
raise NotFoundException(excArgs=request_id + "/" + request_image_id)
else:
return JsonResponse({'error': 'Invalid request method.'}, status=405)

View File

@ -2,7 +2,7 @@ from django.conf import settings
from rest_framework.routers import DefaultRouter, SimpleRouter from rest_framework.routers import DefaultRouter, SimpleRouter
from fwd_api.api.ctel_view import CtelViewSet from fwd_api.api.ctel_view import CtelViewSet
from fwd_api.api.accuracy_view import AccuracyViewSet, RequestViewSet from fwd_api.api.accuracy_view import AccuracyViewSet
from fwd_api.api.ctel_user_view import CtelUserViewSet from fwd_api.api.ctel_user_view import CtelUserViewSet
@ -16,7 +16,6 @@ else:
router.register("ctel", CtelViewSet, basename="CtelAPI") router.register("ctel", CtelViewSet, basename="CtelAPI")
router.register("ctel", CtelUserViewSet, basename="CtelUserAPI") router.register("ctel", CtelUserViewSet, basename="CtelUserAPI")
router.register("ctel", AccuracyViewSet, basename="AccuracyAPI") router.register("ctel", AccuracyViewSet, basename="AccuracyAPI")
router.register("ctel", RequestViewSet, basename="RequestAPI")
app_name = "api" app_name = "api"
urlpatterns = router.urls urlpatterns = router.urls

View File

@ -624,6 +624,8 @@ def predict_result_to_ready(result):
"sold_to_party": "", "sold_to_party": "",
"purchase_date": [], "purchase_date": [],
"imei_number": [],} "imei_number": [],}
if not result:
return dict_result
dict_result["retailername"] = result.get("content", {}).get("document", [{}])[0].get("content", [{}])[0].get("value", None) dict_result["retailername"] = result.get("content", {}).get("document", [{}])[0].get("content", [{}])[0].get("value", None)
dict_result["sold_to_party"] = result.get("content", {}).get("document", [{}])[0].get("content", [{}, {}])[1].get("value", None) dict_result["sold_to_party"] = result.get("content", {}).get("document", [{}])[0].get("content", [{}, {}])[1].get("value", None)
dict_result["purchase_date"] = result.get("content", {}).get("document", [{}])[0].get("content", [{}, {}, {}])[2].get("value", []) dict_result["purchase_date"] = result.get("content", {}).get("document", [{}])[0].get("content", [{}, {}, {}])[2].get("value", [])

View File

@ -429,6 +429,8 @@ def build_media_url_v2(media_id: str, user_id: int, sub_id: int, u_sync_id: str)
token = image_authenticator.generate_img_token_v2(user_id, sub_id, u_sync_id) token = image_authenticator.generate_img_token_v2(user_id, sub_id, u_sync_id)
return f'{settings.BASE_URL}/api/ctel/v2/media/request/{media_id}/?token={token}' return f'{settings.BASE_URL}/api/ctel/v2/media/request/{media_id}/?token={token}'
def build_S3_url(s3_key, exp_time):
return s3_client.create_url_with_expiration(s3_key, exp_time)
def get_value(_dict, keys): def get_value(_dict, keys):
keys = keys.split('.') keys = keys.split('.')

View File

@ -51,6 +51,21 @@ class MinioS3Client:
return res return res
except Exception as e: except Exception as e:
print(f"Error downloading file from S3: {str(e)}") print(f"Error downloading file from S3: {str(e)}")
def create_url_with_expiration(self, s3_key, expiration_time):
try:
res = self.s3_client.generate_presigned_url(
ClientMethod="get_object", ExpiresIn=expiration_time,
Params={
"Bucket": self.bucket_name,
"Key": s3_key,
},
)
# print(f"URL for file '{s3_key}' expires in {expiration_time} seconds")
return res
except Exception as e:
print(f"Error generating URL for file '{s3_key}': {str(e)}")
if __name__=="__main__": if __name__=="__main__":
FILE = "/app/media/users/1/subscriptions/33/requests/sbt_invoice/SAP00c6c229c2954e498b119968a318d366/temp_SAP00c6c229c2954e498b119968a318d366.jpg" FILE = "/app/media/users/1/subscriptions/33/requests/sbt_invoice/SAP00c6c229c2954e498b119968a318d366/temp_SAP00c6c229c2954e498b119968a318d366.jpg"

View File

@ -25,6 +25,7 @@
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-hotkeys-hook": "^4.5.0",
"react-json-view-lite": "^1.2.1", "react-json-view-lite": "^1.2.1",
"react-office-viewer": "^1.0.4", "react-office-viewer": "^1.0.4",
"react-router-dom": "^6.6.1", "react-router-dom": "^6.6.1",
@ -11407,6 +11408,15 @@
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/react-hotkeys-hook": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz",
"integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==",
"peerDependencies": {
"react": ">=16.8.1",
"react-dom": ">=16.8.1"
}
},
"node_modules/react-i18next": { "node_modules/react-i18next": {
"version": "11.18.6", "version": "11.18.6",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.18.6.tgz",

View File

@ -27,27 +27,28 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/colors": "^6.0.0", "@ant-design/colors": "^6.0.0",
"@ant-design/icons": "^4.8.0", "@ant-design/icons": "^4.8.0",
"@ant-design/plots": "^1.2.3", "@ant-design/plots": "^1.2.3",
"@ant-design/pro-layout": "^7.10.3", "@ant-design/pro-layout": "^7.10.3",
"@babel/core": "^7.13.10", "@babel/core": "^7.13.10",
"@cyntler/react-doc-viewer": "^1.14.1", "@cyntler/react-doc-viewer": "^1.14.1",
"@tanstack/react-query": "^4.20.4", "@tanstack/react-query": "^4.20.4",
"antd": "^5.4.0", "antd": "^5.4.0",
"axios": "^1.2.2", "axios": "^1.2.2",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
"history": "^5.3.0", "history": "^5.3.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mousetrap": "^1.6.5", "mousetrap": "^1.6.5",
"process": "^0.11.10", "process": "^0.11.10",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-json-view-lite": "^1.2.1", "react-hotkeys-hook": "^4.5.0",
"react-office-viewer": "^1.0.4", "react-json-view-lite": "^1.2.1",
"react-router-dom": "^6.6.1", "react-office-viewer": "^1.0.4",
"styled-components": "^5.3.6", "react-router-dom": "^6.6.1",
"uuid": "^9.0.0" "styled-components": "^5.3.6",
"uuid": "^9.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-jsx": "^7.12.13", "@babel/plugin-syntax-jsx": "^7.12.13",

View File

@ -34,7 +34,7 @@ const columns: TableColumnsType<DataType> = [
return String(record.subSidiaries).toUpperCase(); return String(record.subSidiaries).toUpperCase();
}, },
filters: [ filters: [
{ text: 'ALL', value: 'ALL' }, { text: 'SEAO', value: 'SEAO' },
{ text: 'SEAU', value: 'SEAU' }, { text: 'SEAU', value: 'SEAU' },
{ text: 'SESP', value: 'SESP' }, { text: 'SESP', value: 'SESP' },
{ text: 'SME', value: 'SME' }, { text: 'SME', value: 'SME' },

View File

@ -4,7 +4,7 @@
"Back to Dashboard": "Back to Dashboard", "Back to Dashboard": "Back to Dashboard",
"Create New Report": "Create New Report", "Create New Report": "Create New Report",
"Dashboard": "Dashboard", "Dashboard": "Dashboard",
"Date": "Date", "Date (GMT+8)": "Date (GMT+8)",
"Download": "Download", "Download": "Download",
"Download Report": "Download Report", "Download Report": "Download Report",
"Duration": "Duration", "Duration": "Duration",
@ -12,6 +12,7 @@
"English": "English", "English": "English",
"Go to Reports": "Go to Reports", "Go to Reports": "Go to Reports",
"Inference": "Inference", "Inference": "Inference",
"Is Test": "Is Test",
"Language": "Language", "Language": "Language",
"Login": "Login", "Login": "Login",
"Logout": "Logout", "Logout": "Logout",
@ -28,8 +29,11 @@
"Please specify a password": "Please specify a password", "Please specify a password": "Please specify a password",
"Please specify a username": "Please specify a username", "Please specify a username": "Please specify a username",
"Report Details": "Report Details", "Report Details": "Report Details",
"Report Filters": "Report Filters",
"Reports": "Reports", "Reports": "Reports",
"Retry": "Retry", "Retry": "Retry",
"Review": "Review",
"Reviewed": "Reviewed",
"Service temporarily unavailable.": "Service temporarily unavailable.", "Service temporarily unavailable.": "Service temporarily unavailable.",
"Something went wrong.": "Something went wrong.", "Something went wrong.": "Something went wrong.",
"Sorry, something went wrong.": "Sorry, something went wrong.", "Sorry, something went wrong.": "Sorry, something went wrong.",

View File

@ -4,7 +4,7 @@
"Back to Dashboard": "", "Back to Dashboard": "",
"Create New Report": "", "Create New Report": "",
"Dashboard": "", "Dashboard": "",
"Date": "", "Date (GMT+8)": "",
"Download": "", "Download": "",
"Download Report": "", "Download Report": "",
"Duration": "", "Duration": "",
@ -12,6 +12,7 @@
"English": "Tiếng Anh", "English": "Tiếng Anh",
"Go to Reports": "", "Go to Reports": "",
"Inference": "", "Inference": "",
"Is Test": "",
"Language": "Ngôn ngữ", "Language": "Ngôn ngữ",
"Login": "Đăng nhập", "Login": "Đăng nhập",
"Logout": "Đăng xuất", "Logout": "Đăng xuất",
@ -28,8 +29,11 @@
"Please specify a password": "Vui lòng nhập một mật khẩu", "Please specify a password": "Vui lòng nhập một mật khẩu",
"Please specify a username": "Vui lòng nhập một tên tài khoản", "Please specify a username": "Vui lòng nhập một tên tài khoản",
"Report Details": "", "Report Details": "",
"Report Filters": "",
"Reports": "", "Reports": "",
"Retry": "Thử lại", "Retry": "Thử lại",
"Review": "",
"Reviewed": "",
"Service temporarily unavailable.": "Dịch vụ máy chủ hiện tại không sẵn sàng.", "Service temporarily unavailable.": "Dịch vụ máy chủ hiện tại không sẵn sàng.",
"Something went wrong.": "Có lỗi xảy ra", "Something went wrong.": "Có lỗi xảy ra",
"Sorry, something went wrong.": "Hệ thống gặp lỗi", "Sorry, something went wrong.": "Hệ thống gặp lỗi",

View File

@ -16,7 +16,7 @@ export interface ReportFormValues {
const Dashboard = () => { const Dashboard = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [duration, setDuration] = useState<string>('30d'); const [duration, setDuration] = useState<string>('30d');
const [subsidiary, setSubsidiary] = useState<string>('ALL'); const [subsidiary, setSubsidiary] = useState<string>('SEAO');
const [form] = Form.useForm<ReportFormValues>(); const [form] = Form.useForm<ReportFormValues>();
const { isLoading, data } = useOverViewReport({ const { isLoading, data } = useOverViewReport({
duration: duration, duration: duration,
@ -93,6 +93,7 @@ const Dashboard = () => {
style={{ width: 200 }} style={{ width: 200 }}
options={[ options={[
{ value: 'ALL', label: 'ALL' }, { value: 'ALL', label: 'ALL' },
{ value: 'SEAO', label: 'SEAO' },
{ value: 'SEAU', label: 'SEAU' }, { value: 'SEAU', label: 'SEAU' },
{ value: 'SESP', label: 'SESP' }, { value: 'SESP', label: 'SESP' },
{ value: 'SME', label: 'SME' }, { value: 'SME', label: 'SME' },
@ -100,7 +101,7 @@ const Dashboard = () => {
{ value: 'TSE', label: 'TSE' }, { value: 'TSE', label: 'TSE' },
{ value: 'SEIN', label: 'SEIN' }, { value: 'SEIN', label: 'SEIN' },
]} ]}
defaultValue='ALL' defaultValue='SEAO'
value={subsidiary} value={subsidiary}
onChange={(value) => { onChange={(value) => {
setSubsidiary(value); setSubsidiary(value);

View File

@ -112,7 +112,7 @@ const ReportsPage = () => {
placeholder='Select a subsidiary' placeholder='Select a subsidiary'
style={{ width: 200 }} style={{ width: 200 }}
options={[ options={[
{ value: 'ALL', label: 'ALL' }, { value: 'SEAO', label: 'SEAO' },
{ value: 'SEAU', label: 'SEAU' }, { value: 'SEAU', label: 'SEAU' },
{ value: 'SESP', label: 'SESP' }, { value: 'SESP', label: 'SESP' },
{ value: 'SME', label: 'SME' }, { value: 'SME', label: 'SME' },

View File

@ -1,10 +1,23 @@
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { Button, message, Upload, Input, Table } from 'antd'; import { Button, Input, Table, Tag, DatePicker, Form, Modal, Select, Space, Spin, message } from 'antd';
import { SbtPageHeader } from 'components/page-header'; import React, { useContext, useEffect, useRef, useState } from 'react';
import { useState } from 'react'; import type { GetRef } from 'antd';
import { Layout } from 'antd'; import { Layout } from 'antd';
import {
DownloadOutlined, CheckCircleOutlined,
ArrowLeftOutlined,
ArrowRightOutlined,
FullscreenOutlined,
FullscreenExitOutlined,
ClockCircleFilled,
} from '@ant-design/icons';
import FileViewer from '@cyntler/react-doc-viewer'; import FileViewer from '@cyntler/react-doc-viewer';
import styled from 'styled-components';
const { Sider, Content } = Layout; const { Sider, Content } = Layout;
import { baseURL } from "request/api";
import moment from 'moment';
import { useHotkeys } from "react-hotkeys-hook";
const siderStyle: React.CSSProperties = { const siderStyle: React.CSSProperties = {
backgroundColor: '#fafafa', backgroundColor: '#fafafa',
@ -13,61 +26,142 @@ const siderStyle: React.CSSProperties = {
}; };
const fileList = [ const StyledTable = styled(Table)`
{ & .sbt-table-cell {
name: "invoice.pdf", padding: 4px!important;
url: "/dummpy.pdf",
type: "invoice",
isBadQuality: false,
},
{
name: "invoice.pdf",
url: "/dummpy.pdf",
type: "imei",
isBadQuality: true,
} }
] `;
const dataSource = [ type InputRef = GetRef<typeof Input>;
{ type FormInstance<T> = GetRef<typeof Form<T>>;
key: '1',
value: 'Mike',
},
{
key: '2',
value: 'Mike',
},
{
key: '3',
value: 'Mike',
},
];
const columns = [ const EditableContext = React.createContext<FormInstance<any> | null>(null);
interface Item {
key: string;
accuracy: number;
submitted: string;
revised: string;
action: string;
}
interface EditableRowProps {
index: number;
}
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof Item;
record: Item;
handleSave: (record: Item) => void;
}
const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
// type EditableTableProps = Parameters<typeof Table>[0];
const defaultColumns = [
{ {
title: 'Key', title: 'Key',
dataIndex: 'key', dataIndex: 'key',
key: 'key', key: 'key',
width: 200,
}, },
{ {
title: 'Predicted', title: 'Predicted',
dataIndex: 'value', dataIndex: 'predicted',
key: 'value', key: 'predicted',
}, },
{ {
title: 'Submitted', title: 'Submitted',
dataIndex: 'value', dataIndex: 'submitted',
key: 'value', key: 'submitted',
}, },
{ {
title: 'Revised', title: 'Revised',
dataIndex: 'value', dataIndex: 'revised',
key: 'value', key: 'revised',
editable: true,
}, },
]; ];
const FileCard = ({ file, isSelected, onClick }) => { const FileCard = ({ file, isSelected, onClick, setIsReasonModalOpen }) => {
const fileName = file["File Name"];
return ( return (
<div style={{ <div style={{
border: '1px solid #ccc', border: '1px solid #ccc',
@ -75,7 +169,10 @@ const FileCard = ({ file, isSelected, onClick }) => {
backgroundColor: isSelected ? '#d4ecff' : '#fff', backgroundColor: isSelected ? '#d4ecff' : '#fff',
padding: '4px 8px', padding: '4px 8px',
marginRight: '4px', marginRight: '4px',
marginTop: '4px', marginTop: '2px',
position: 'relative',
height: '100px',
overflow: 'hidden',
}} onClick={onClick}> }} onClick={onClick}>
<div> <div>
<span style={{ <span style={{
@ -83,99 +180,644 @@ const FileCard = ({ file, isSelected, onClick }) => {
color: '#333', color: '#333',
fontWeight: 'bold', fontWeight: 'bold',
padding: '4px 8px', padding: '4px 8px',
}}>{file.type.toUpperCase()}</span> cursor: 'default',
}}>{file["Doc Type"].toUpperCase()}</span>
<span style={{ <span style={{
fontSize: '12px', fontSize: '12px',
color: '#aaa', color: '#aaa',
fontWeight: 'bold', fontWeight: 'bold',
padding: '4px 8px', padding: '4px 8px',
}}>{file.name}</span> cursor: 'default',
maxWidth: '50px',
overflow: 'hidden',
textOverflow: 'ellipsis',
}}>
{fileName ? fileName.substring(0, 25).replace("temp_", "") : fileName}
</span>
</div>
<div style={{
padding: '4px',
position: 'absolute',
bottom: 2,
right: 2,
}}>
<Button style={{
margin: '4px 2px',
}}
onClick={() => {
setIsReasonModalOpen(true);
}}
>
Review
</Button>
<Button style={{
margin: '4px 2px',
}} onClick={() => {
const downloadUrl = file["File URL"];
window.open(downloadUrl, '_blank');
}}>
<DownloadOutlined />
</Button>
</div> </div>
</div> </div>
); );
}; };
const InferencePage = () => { const fetchAllRequests = async (filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, page = 1, page_size = 20) => {
const startDate = (filterDateRange && filterDateRange[0]) ? filterDateRange[0] : '';
const endDate = (filterDateRange && filterDateRange[1]) ? filterDateRange[1] : '';
let filterStr = "";
filterStr += `page=${page}&page_size=${page_size}&`;
if (filterSubsidiaries) {
filterStr += `subsidiary=${filterSubsidiaries}&`;
}
if (filterReviewState) {
filterStr += `is_reviewed=${filterReviewState}&`;
}
if (filterIncludeTests) {
filterStr += `includes_test=${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];
};
const ReviewPage = () => {
const [loading, setLoading] = useState(false);
const [fullscreen, setFullscreen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [isReasonModalOpen, setIsReasonModalOpen] = useState(false);
const [selectedFileId, setSelectedFileId] = useState(0); const [selectedFileId, setSelectedFileId] = useState(0);
const selectFileByIndex = (index) => { const [selectedFileData, setSelectedFileData] = useState(null);
const [selectedFileName, setSelectedFileName] = useState(null);
// Default date range: 1 month ago to today
const [filterDateRange, setFilterDateRange] = useState([
"", ""
]);
const [filterSubsidiaries, setFilterSubsidiaries] = useState('SEAO');
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 [totalRequests, setTotalPages] = useState(0);
const [dataSource, setDataSource] = useState([]);
const [pageIndexToGoto, setPageIndexToGoto] = useState(1);
const setAndLoadSelectedFile = async (requestData, index) => {
setSelectedFileId(index); setSelectedFileId(index);
if (!requestData["Files"][index]) {
setSelectedFileData("FAILED_TO_LOAD_FILE");
return;
};
const fileName = requestData["Files"][index]["File Name"];
const fileURL = requestData["Files"][index]["File URL"];
const response = await fetch(fileURL);
if (response.status === 200) {
setSelectedFileName(fileName);
setSelectedFileData(fileURL);
console.log("Loading file: " + fileName);
console.log("URL: " + fileURL);
} else {
setSelectedFileData("FAILED_TO_LOAD_FILE");
}
}; };
const loadCurrentRequest = (requestID) => {
setLoading(true);
fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, requestID, 2).then((data) => {
setRequests(data?.subscription_requests);
setHasNextRequest(data?.subscription_requests.length > 1);
setTotalPages(data?.page?.total_requests);
const requestData = fetchRequest(data?.subscription_requests[0].RequestID);
requestData.then(async (data) => {
if (data) setCurrentRequest(data);
const predicted = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {};
const submitted = (data && data["Feedback Result"]) ? data["Feedback Result"] : {};
const revised = (data && data["Reviewed Result"]) ? data["Reviewed Result"] : {};
const keys = Object.keys(predicted);
const tableRows = [];
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]];
tableRows.push(instance);
}
setDataSource(tableRows);
setLoading(false);
setAndLoadSelectedFile(data, 0);
}).finally(() => {
setLoading(false);
});
}).finally(() => {
setLoading(false);
});
};
const gotoNextRequest = () => {
if (currentRequestIndex >= totalRequests) {
return;
}
const nextRequestIndex = currentRequestIndex + 1;
setCurrentRequestIndex(nextRequestIndex);
loadCurrentRequest(nextRequestIndex);
};
const gotoPreviousRequest = () => {
if (currentRequestIndex === 1) {
return;
}
const previousRequestIndex = currentRequestIndex - 1;
setCurrentRequestIndex(previousRequestIndex);
loadCurrentRequest(previousRequestIndex);
};
const reloadFilters = () => {
setCurrentRequestIndex(1);
fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, currentRequestIndex, 2).then((data) => {
setTotalPages(data?.page?.total_requests);
setRequests(data?.subscription_requests);
setHasNextRequest(data?.subscription_requests.length > 1);
const firstRequest = fetchRequest(data?.subscription_requests[0].RequestID);
firstRequest.then(async (data) => {
if (data) setCurrentRequest(data);
setAndLoadSelectedFile(data, 0);
});
});
};
useEffect(() => {
setCurrentRequestIndex(1);
fetchAllRequests(filterDateRange, filterSubsidiaries, filterReviewState, filterIncludeTests, currentRequestIndex, 2).then((data) => {
setTotalPages(data?.page?.total_requests);
setRequests(data?.subscription_requests);
setHasNextRequest(data?.subscription_requests.length > 1);
const firstRequest = fetchRequest(data?.subscription_requests[0].RequestID);
firstRequest.then(async (data) => {
if (data) setCurrentRequest(data);
setAndLoadSelectedFile(data, 0);
});
});
}, []);
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
// "Key", "Accuracy", "Submitted", "Revised"
interface DataType {
key: string;
accuracy: number;
submitted: string;
revised: string;
};
const updateRevisedData = async (newRevisedData: any) => {
const requestID = newRevisedData.request_id;
const token = localStorage.getItem('sbt-token') || '';
await fetch(`${baseURL}/ctel/request/${requestID}/`, {
method: 'POST',
headers: {
"Authorization": `${JSON.parse(token)}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
"reviewed_result": newRevisedData
}),
}).catch((error) => {
console.log(error);
message.error("Could not update revised data");
});
};
const handleSave = (row: DataType) => {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
setDataSource(newData);
const newRevisedData = {};
for (let i = 0; i < newData.length; i++) {
newRevisedData[newData[i].key] = newData[i].revised;
}
updateRevisedData(newRevisedData).then(() => {
// "[Is Reviewed]" => true
setCurrentRequest({
...currentRequest,
["Is Reviewed"]: true,
})
})
};
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: DataType) => ({
record,
editable: col.key != "request_id" && col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
// use left/right keys to navigate
useHotkeys("left", gotoPreviousRequest);
useHotkeys("right", gotoNextRequest);
const fileExtension = selectedFileName ? selectedFileName.split('.').pop() : '';
return ( return (
<> <div style={fullscreen ? {
{/* <SbtPageHeader position: 'fixed',
title={t`Result Review`} top: 0,
/> */} left: 0,
width: '100%',
height: '100%',
backgroundColor: '#fff',
zIndex: 1000,
} : {
height: '100%',
position: 'relative',
}}>
<div
style={{ height: '100%', position: 'absolute', top: 0, left: 0, width: '100%', background: "#00000033", zIndex: 1000, display: loading ? 'block' : 'none' }}
>
<Spin spinning={true} style={{
position: 'absolute',
top: '50%',
left: '50%',
marginTop: -12,
marginLeft: -12,
width: 24,
height: 24,
borderRadius: '50%',
}} size='large' />
</div>
<div>
<Button onClick={() => {
setFullscreen(!fullscreen);
}}>
{fullscreen ? <FullscreenExitOutlined /> : <FullscreenOutlined />}
{fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}
</Button>
{totalRequests ? <>&nbsp;&nbsp;&nbsp;<b>Request ID:</b> {currentRequest?.RequestID}</> : ""}
</div>
<Layout style={{ <Layout style={{
overflow: 'hidden', overflow: 'auto',
width: '100%', width: '100%',
height: '100%',
maxWidth: '100%', maxWidth: '100%',
minHeight: 'calc(100vh - 100px)', minHeight: '70%',
maxHeight: 'calc(100vh - 100px)', maxHeight: '70%',
display: 'flex', display: 'flex',
padding: '8px', padding: '8px',
}}> }}>
<Content style={{ <Content style={{
textAlign: 'center', textAlign: 'center',
color: '#fff', color: '#fff',
backgroundColor: '#efefef', backgroundColor: '#efefef',
height: '100%', height: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'row',
flexGrow: 1, }}>
}}> {totalRequests > 0 && <div
<div style={{ style={{
border: "1px solid #ccc", width: "200px",
flexGrow: 1, display: "flex",
height: '500px', flexDirection: "column",
flexGrow: 0,
}}> }}>
<FileViewer documents={ <h2
[
{ uri: "/dummy.pdf" }
]
} config={{
header: {
disableHeader: true,
disableFileName: true,
retainURLParams: true,
},
csvDelimiter: ",", // "," as default,
pdfVerticalScrollByDefault: true, // false as default
}} />
</div>
<div
style={{ style={{
width: "100%", color: "#333",
display: "flex", padding: 10,
flexDirection: "row", fontWeight: 'bold'
height: "100px", }}
flexGrow: 0, >Files ({currentRequest?.Files?.length})</h2>
}}> {currentRequest?.Files.map((file, index) => (
{fileList.map((file, index) => ( <FileCard key={index} file={file} isSelected={index === selectedFileId} onClick={
<FileCard key={index} file={file} isSelected={index === selectedFileId} onClick={ () => {
() => { setAndLoadSelectedFile(currentRequest, index);
setSelectedFileId(index); }
} setIsReasonModalOpen={setIsReasonModalOpen} />
))}
</div>}
<div style={{
border: "1px solid #ccc",
flexGrow: 1,
height: '100%',
}}>
{selectedFileData === "FAILED_TO_LOAD_FILE" ? <p style={{ color: "#333" }}>Failed to load file.</p> : ( fileExtension === "pdf" ? (<FileViewer documents={[
{ uri: selectedFileData }
]} config={{
header: {
disableHeader: true,
disableFileName: true,
retainURLParams: true,
},
csvDelimiter: ",", // "," as default,
pdfVerticalScrollByDefault: true, // false as default
}} />) : <div style={{
height: "100%",
width: "100%",
overflow: "auto",
}}><img width={"100%"} src={selectedFileData} alt="file" /></div>)}
</div>
</Content>
<Sider width="400px" style={siderStyle}>
<Space.Compact style={{ width: '100%', marginBottom: 16 }}>
<Input value={
`Sub: ${filterSubsidiaries}, Date:${filterDateRange[0] ? (filterDateRange[0] + " to " + filterDateRange[1]) : "All"}, Reviewed: ${filterReviewState}, Tests: ${filterIncludeTests}`
} readOnly />
<Button type="primary" size="large"
onClick={() => {
setIsModalOpen(true);
}}
>
Filters
</Button>
</Space.Compact>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 8 }}>
<div>
<Button type="default" style={{ height: 38 }}
disabled={currentRequestIndex === 1}
onClick={() => {
gotoPreviousRequest();
}}
>
<ArrowLeftOutlined />
Previou
</Button>
<Button type="default" style={{ height: 38 }}
disabled={!hasNextRequest}
onClick={() => {
if (!hasNextRequest) {
return;
} }
} /> gotoNextRequest();
))} }}
>
Next
<ArrowRightOutlined />
</Button>
<Input size='middle' addonBefore="To" style={{ marginBottom: "4px", marginLeft: "4px", width: 180 }} defaultValue={currentRequestIndex} addonAfter={
<Button type="default"
onClick={() => {
if (pageIndexToGoto > totalRequests) {
message.error("RequestID is out of range.");
return;
}
if (pageIndexToGoto < 1) {
message.error("RequestID is out of range.");
return;
}
setCurrentRequestIndex(pageIndexToGoto);
loadCurrentRequest(pageIndexToGoto);
}}>
Go to
</Button>
}
value={pageIndexToGoto}
onChange={e => {
setPageIndexToGoto(parseInt(e.target.value));
}}
/>
</div> </div>
</Content> </div>
<Sider width="400px" style={siderStyle}> <h2 style={{ margin: "20px 0 10px 0" }}>{totalRequests ? ("Request: " + currentRequestIndex + "/" + totalRequests) : "No Request. Adjust your search criteria to see more results."}</h2>
<h2 style={{ margin: "0 0 10px 0" }}>Overview</h2> {totalRequests > 0 && <div>
<Input size='small' addonBefore="Request ID" style={{ marginBottom: "4px" }} readOnly /> <Input size='small' addonBefore="Request ID" style={{ marginBottom: "4px" }} readOnly value={currentRequest ? currentRequest.RequestID : ""} />
<Input size='small' addonBefore="Redemption" style={{ marginBottom: "4px" }} readOnly /> <Input size='small' addonBefore="Redemption" style={{ marginBottom: "4px" }} readOnly value={currentRequest?.RedemptionID ? currentRequest.RedemptionID : ""} />
<Input size='small' addonBefore="Uploaded date" style={{ marginBottom: "4px" }} readOnly /> <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 /> <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 /> <Input size='small' addonBefore="Processing time" style={{ marginBottom: "4px" }} readOnly value={currentRequest ? currentRequest["Server Processing Time (ms)"] : ""} />
<div style={{ marginBottom: "8px", marginTop: "8px", display: "flex" }}> <div style={{ marginBottom: "8px", marginTop: "8px", display: "flex" }}>
<Button type="primary" size='middle'>Confirm result</Button> {currentRequest && (currentRequest["Is Reviewed"] ? <Tag icon={<CheckCircleOutlined />} color="success" style={{ padding: "4px 16px" }}>
Reviewed
</Tag> : <Tag icon={<ClockCircleFilled />} color="warning" style={{ padding: "4px 16px" }}>
Not Reviewed
</Tag>)}
</div> </div>
<Table dataSource={dataSource} columns={columns} /> </div>}
</Sider> </Sider>
</Layout> </Layout>
</> <Modal
title={t`Report Filters`}
open={isModalOpen}
width={700}
onOk={
() => {
setIsModalOpen(false);
reloadFilters();
}
}
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
onChange={(date, dateString) => {
setFilterDateRange(dateString);
}}
style={{ width: 200 }}
/>
</Form.Item>
<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: 'SEAO', label: 'SEAO' },
{ 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 }}>
<Form.Item
name='reviewed'
label={t`Reviewed`}
rules={[
{
required: true,
message: 'Please select review status',
},
]}
>
<Select
style={{ width: 200 }}
options={[
{ label: 'All', value: 'all' },
{ label: 'Reviewed', value: 'reviewed' },
{ label: 'Not Reviewed', value: 'not_reviewed' },
]}
value={filterReviewState}
defaultValue={filterReviewState}
onChange={setFilterReviewState}
/>
</Form.Item>
<Form.Item
name='is_test'
label={t`Is Test`}
rules={[
{
required: true,
message: 'Please select test status',
},
]}
style={{ marginLeft: 16 }}
>
<Select
style={{ width: 200 }}
options={[
{ label: 'Include tests', value: 'true' },
{ label: 'Exclude tests', value: 'false' },
]}
value={filterIncludeTests}
defaultValue={filterIncludeTests}
onChange={setFilterIncludesTests}
/>
</Form.Item>
</div>
</Form>
</Modal>
<Modal
title={t`Review`}
open={isReasonModalOpen}
width={700}
onOk={
() => {
}
}
onCancel={
() => {
setIsReasonModalOpen(false);
}
}
>
{currentRequest && JSON.stringify(currentRequest["Files"][selectedFileId])}
<Form
style={{
marginTop: 30,
}}
>
<Form.Item
name='reason'
label={t`Reason for bad quality:`}
style={{
marginBottom: 10,
}}
>
<Select
placeholder='Select a reason'
style={{ width: 200 }}
options={[
{ value: 'invalid_image', label: t`Invalid image` },
{ value: 'missing_information', label: t`Missing information` },
{ value: 'too_blurry_text', label: t`Too blurry text` },
{ value: 'too_small_text', label: t`Too small text` },
{ value: 'handwritten', label: t`Handwritten` },
{ value: 'recheck', label: t`Recheck` },
]}
/>
</Form.Item>
</Form>
</Modal>
{totalRequests > 0 && <div
style={{
height: '25%',
overflowY: 'auto',
}}
>
<StyledTable components={components}
rowClassName={() => 'editable-row'}
bordered dataSource={dataSource} columns={columns}
/>
</div>}
</div>
); );
}; };
export default InferencePage; export default ReviewPage;

View File

@ -11,7 +11,7 @@ const environment = process.env.NODE_ENV;
const AXIOS_TIMEOUT_MS = 30 * 60 * 1000; // This config sastified long-live upload file request const AXIOS_TIMEOUT_MS = 30 * 60 * 1000; // This config sastified long-live upload file request
const EXPIRED_PASSWORD_SIGNAL = 'expired_password'; const EXPIRED_PASSWORD_SIGNAL = 'expired_password';
export const baseURL = environment === 'development' ? 'http://42.96.42.13:9000/api' : '/api'; export const baseURL = environment === 'development' ? 'http://42.96.42.13:9881/api' : '/api';
export const API = axios.create({ export const API = axios.create({
timeout: AXIOS_TIMEOUT_MS, timeout: AXIOS_TIMEOUT_MS,

View File

@ -123,7 +123,7 @@ export async function downloadReport(report_id: string, downloadFinishedCallback
} }
export async function downloadDashboardReport(duration='30d', subsidiary='ALL') { export async function downloadDashboardReport(duration='30d', subsidiary='SEAO') {
try { try {
const response = await API.get(`/ctel/overview_download_file/?duration=${duration}&subsidiary=${subsidiary}`, { const response = await API.get(`/ctel/overview_download_file/?duration=${duration}&subsidiary=${subsidiary}`, {
responseType: 'blob', // Important responseType: 'blob', // Important