From b4efb409784c490f69cac30143ea3432336a4aa8 Mon Sep 17 00:00:00 2001 From: phanphan Date: Thu, 2 May 2024 19:14:47 +0700 Subject: [PATCH 1/3] update design --- cope2n-fe/package.json | 2 + cope2n-fe/src/layouts/main-layout.tsx | 2 +- cope2n-fe/src/pages/reviews/FileCard.tsx | 1 + cope2n-fe/src/pages/reviews2/FileCard.tsx | 77 ++ cope2n-fe/src/pages/reviews2/api.ts | 79 ++ cope2n-fe/src/pages/reviews2/consts.ts | 10 + cope2n-fe/src/pages/reviews2/index.tsx | 1394 +++++++++++++++++++++ cope2n-fe/src/routes/useAppRouter.tsx | 6 + 8 files changed, 1570 insertions(+), 1 deletion(-) create mode 100644 cope2n-fe/src/pages/reviews2/FileCard.tsx create mode 100644 cope2n-fe/src/pages/reviews2/api.ts create mode 100644 cope2n-fe/src/pages/reviews2/consts.ts create mode 100644 cope2n-fe/src/pages/reviews2/index.tsx diff --git a/cope2n-fe/package.json b/cope2n-fe/package.json index 7798b18..447642a 100644 --- a/cope2n-fe/package.json +++ b/cope2n-fe/package.json @@ -44,6 +44,7 @@ "pdfjs-dist": "^3.11.174", "process": "^0.11.10", "react": "^18.2.0", + "react-awesome-lightbox": "^1.8.1", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", "react-hotkeys-hook": "^4.5.0", @@ -51,6 +52,7 @@ "react-office-viewer": "^1.0.4", "react-router-dom": "^6.6.1", "styled-components": "^5.3.6", + "ts-node": "^10.9.2", "uuid": "^9.0.0" }, "devDependencies": { diff --git a/cope2n-fe/src/layouts/main-layout.tsx b/cope2n-fe/src/layouts/main-layout.tsx index c77c5e0..eee7337 100644 --- a/cope2n-fe/src/layouts/main-layout.tsx +++ b/cope2n-fe/src/layouts/main-layout.tsx @@ -119,7 +119,7 @@ export const MainLayout = ({ children }: { children: React.ReactNode }) => { style={{ height: '100%', overflow: 'auto', - padding: 32, + padding: 16, background: colorBgContainer, }} > diff --git a/cope2n-fe/src/pages/reviews/FileCard.tsx b/cope2n-fe/src/pages/reviews/FileCard.tsx index 363e5c6..1c3fd82 100644 --- a/cope2n-fe/src/pages/reviews/FileCard.tsx +++ b/cope2n-fe/src/pages/reviews/FileCard.tsx @@ -31,6 +31,7 @@ const FileCard = ({ file, isSelected, onClick, setIsReasonModalOpen }) => { > {file['Doc Type'].toUpperCase()} +
{ + const fileName = file['File Name']; + + return ( +
+
+

+ {file['Doc Type'].toUpperCase()} +

+ + {fileName ? fileName.substring(0, 25).replace('temp_', '') : fileName} + +
+
+ + +
+
+ ); +}; + +export default FileCard; diff --git a/cope2n-fe/src/pages/reviews2/api.ts b/cope2n-fe/src/pages/reviews2/api.ts new file mode 100644 index 0000000..a8c1885 --- /dev/null +++ b/cope2n-fe/src/pages/reviews2/api.ts @@ -0,0 +1,79 @@ +import { baseURL } from 'request/api'; + +export const fetchAllRequests = async ( + filterDateRange, + filterSubsidiaries, + filterReviewState, + filterIncludeTests, + page = 1, + page_size = 20, + max_accuracy = 100 +) => { + 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}&`; + } + filterStr += `max_accuracy=${max_accuracy}` + 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; +}; + +export const updateRevisedData = async ( + requestID: any, + newRevisedData: any, +) => { + // const requestID = ; + const token = localStorage.getItem('sbt-token') || ''; + const result = 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); + throw error; + }); + if (result.status != 200) { + throw new Error('Could not update revised data'); + } +}; + +export 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]; +}; diff --git a/cope2n-fe/src/pages/reviews2/consts.ts b/cope2n-fe/src/pages/reviews2/consts.ts new file mode 100644 index 0000000..6f5ddcf --- /dev/null +++ b/cope2n-fe/src/pages/reviews2/consts.ts @@ -0,0 +1,10 @@ +export const counter_measure_map = { + invalid_image: 'Remove this image from the evaluation report', + missing_information: 'Remove this image from the evaluation report', + too_blurry_text: 'Remove this image from the evaluation report', + too_small_text: 'Remove this image from the evaluation report', + ocr_cannot_extract: 'Improve OCR', + wrong_feedback: 'Update revised result and re-calculate accuracy', + handwritten: 'Remove this image from the evaluation report', + other: 'other', +}; diff --git a/cope2n-fe/src/pages/reviews2/index.tsx b/cope2n-fe/src/pages/reviews2/index.tsx new file mode 100644 index 0000000..6204de3 --- /dev/null +++ b/cope2n-fe/src/pages/reviews2/index.tsx @@ -0,0 +1,1394 @@ +import { + ArrowLeftOutlined, + ArrowRightOutlined, + CheckCircleOutlined, + ClockCircleFilled, + FullscreenExitOutlined, + FullscreenOutlined, +} from '@ant-design/icons'; +import { t } from '@lingui/macro'; +import { Viewer } from '@react-pdf-viewer/core'; +import type { GetRef } from 'antd'; +import { + Button, + DatePicker, + Form, + Input, + InputNumber, + message, + Modal, + notification, + Select, + Spin, + Table, + Tag, +} from 'antd'; +import React, { useContext, useEffect, useRef, useState } from 'react'; +import Lightbox from 'react-awesome-lightbox'; +import 'react-awesome-lightbox/build/style.css'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { baseURL } from 'request/api'; +import styled from 'styled-components'; +// Import the styles +import '@react-pdf-viewer/core/lib/styles/index.css'; + +import { badQualityReasonSubmit } from 'request'; +import { normalizeData } from 'utils/field-value-process'; +import { fetchAllRequests, fetchRequest } from './api'; +import { counter_measure_map } from './consts'; +import FileCard from './FileCard'; + +const siderStyle: React.CSSProperties = { + backgroundColor: '#fafafa', + padding: 10, + flexBasis: 400, + flexShrink: 0, +}; + +const StyledTable = styled(Table)` + & .sbt-table-cell { + padding: 4px !important; + } +`; + +type InputRef = GetRef; +type FormInstance = GetRef>; + +const EditableContext = React.createContext | null>(null); + +interface Item { + key: string; + accuracy: number; + submitted: string; + revised: string; + action: string; +} + +interface EditableRowProps { + index: number; +} + +const EditableRow: React.FC = ({ index, ...props }) => { + const [form] = Form.useForm(); + return ( +
+ + + +
+ ); +}; + +interface EditableCellProps { + title: React.ReactNode; + editable: boolean; + children: React.ReactNode; + dataIndex: keyof Item; + record: Item; + handleSave: (record: Item) => void; +} + +const EditableCell: React.FC = ({ + title, + editable, + children, + dataIndex, + record, + handleSave, + ...restProps +}) => { + const [editing, setEditing] = useState(false); + const inputRef = useRef(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 ? ( + + + + ) : ( +
+ {children} +
+ ); + } + + return {childNode}; +}; + +// type EditableTableProps = Parameters[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 [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 [filterAccuracy, setFilterAccuracy] = useState(100); + 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 [reason, setReason] = useState(''); + const [otherReason, setOtherReason] = useState(''); + const [solution, setSolution] = useState(''); + const [otherSolution, setOtherSolution] = useState(''); + + useEffect(() => { + if (reason) { + setSolution(counter_measure_map[reason]); + } + }, [reason]); + + const setAndLoadSelectedFile = async (requestData, 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'); + } + }; + + console.log(dataSource); + const loadCurrentRequest = (requestIndex) => { + setLoading(true); + fetchAllRequests( + filterDateRange, + filterSubsidiaries, + filterReviewState, + filterIncludeTests, + requestIndex, + 1, + filterAccuracy, + ) + .then((data) => { + // setRequests(data?.subscription_requests); + // setHasNextRequest(data?.subscription_requests.length > 1); + setTotalPages(data?.page?.total_requests); + setHasNextRequest(requestIndex < data?.page?.total_requests); + const requestData = fetchRequest( + data?.subscription_requests[0].RequestID, + ); + requestData + .then(async (data) => { + console.log('🚀 ~ .then ~ data:', data); + if (data) setCurrentRequest(data); + const predicted = + data && data['Predicted Result'] ? data['Predicted 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, + 1, + 1, + filterAccuracy, + ).then((data) => { + setTotalPages(data?.page?.total_requests); + // setRequests(data?.subscription_requests); + // setHasNextRequest(data?.subscription_requests.length > 1); + setHasNextRequest(1 < data?.page?.total_requests); + const firstRequest = fetchRequest( + data?.subscription_requests[0].RequestID, + ); + firstRequest.then(async (data) => { + if (data) setCurrentRequest(data); + setAndLoadSelectedFile(data, 0); + setTimeout(() => { + loadCurrentRequest(1); + }, 500); + }); + }); + }; + + useEffect(() => { + setCurrentRequestIndex(1); + fetchAllRequests( + filterDateRange, + filterSubsidiaries, + filterReviewState, + filterIncludeTests, + 1, + 1, + filterAccuracy, + ).then((data) => { + setTotalPages(data?.page?.total_requests); + // setRequests(data?.subscription_requests); + setHasNextRequest(1 < data?.page?.total_requests); + 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 = currentRequest.RequestID; + const token = localStorage.getItem('sbt-token') || ''; + const result = 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); + throw error; + }); + if (result.status != 200) { + throw new 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, + }); + const newRevisedData = {}; + for (let i = 0; i < newData.length; i++) { + newData[i].revised = normalizeData(newData[i].key, newData[i].revised); + newRevisedData[newData[i].key] = newData[i].revised; + } + updateRevisedData(newRevisedData) + .then(() => { + // "[Is Reviewed]" => true + setCurrentRequest({ + ...currentRequest, + ['Is Reviewed']: true, + }); + }) + .then(() => { + setDataSource(newData); + }) + .catch((error) => { + message.error( + 'Could not update revised data. Please check the format.', + ); + }); + }; + + const submitRevisedData = async () => { + const newData = [...dataSource]; + const newRevisedData = {}; + for (let i = 0; i < newData.length; i++) { + newData[i].revised = normalizeData(newData[i].key, newData[i].revised); + newRevisedData[newData[i].key] = newData[i].revised; + } + updateRevisedData(newRevisedData).then(() => { + // "[Is Reviewed]" => true + setCurrentRequest({ + ...currentRequest, + ['Is Reviewed']: true, + }); + }); + }; + + const defaultColumns = [ + { + title: 'Key', + dataIndex: 'key', + key: 'key', + width: 200, + }, + { + title: 'Predicted', + dataIndex: 'predicted', + key: 'predicted', + render: (text) => { + if (!text) return {''}; + const displayedContent = text; + if (typeof displayedContent === 'string') { + return {displayedContent}; + } else if (typeof displayedContent === 'object') { + if (displayedContent.length === 0) { + return {''}; + } + // Set all empty values to "" + for (const key in displayedContent) { + if (!displayedContent[key]) { + displayedContent[key] = ''; + } + } + return ( + + {displayedContent.join(', ')} + + ); + } + return {displayedContent}; + }, + }, + { + title: 'Submitted', + dataIndex: 'submitted', + key: 'submitted', + render: (text) => { + if (!text) return {''}; + const displayedContent = text; + if (typeof displayedContent === 'string') { + return {displayedContent}; + } else if (typeof displayedContent === 'object') { + if (displayedContent.length === 0) { + return {''}; + } + // Set all empty values to "" + for (const key in displayedContent) { + if (!displayedContent[key]) { + displayedContent[key] = ''; + } + } + return ( + + {displayedContent.join(', ')} + + ); + } + return {displayedContent}; + }, + }, + { + title: ( +
+ Revised   + + +
+ ), + dataIndex: 'revised', + key: 'revised', + editable: true, + render: (text) => { + if (!text) return {''}; + const displayedContent = text; + if (typeof displayedContent === 'string') { + return {displayedContent}; + } else if (typeof displayedContent === 'object') { + if (displayedContent.length === 0) { + return {''}; + } + // Set all empty values to "" + for (const key in displayedContent) { + if (!displayedContent[key]) { + displayedContent[key] = ''; + } + } + return ( + + {displayedContent.join(', ')} + + ); + } + return {displayedContent}; + }, + }, + ]; + + 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() + : ''; + + const [lightBox, setLightBox] = useState(false); + + return ( +
+
+ +
+
+
+ + {totalRequests ? ( + <> +    Request ID: {currentRequest?.RequestID} + + ) : ( + '' + )} +
+
+ + +
+
+
+
+ {totalRequests > 0 && ( +
+
+

+ Files ({currentRequest?.Files?.length}) +

+ {currentRequest?.Files.map((file, index) => ( + { + setAndLoadSelectedFile(currentRequest, index); + }} + setIsReasonModalOpen={setIsReasonModalOpen} + /> + ))} +
+ {totalRequests > 0 && ( +
+ Request ID + + Redemption + + Uploaded date + + Request time + + Processing time + + Raw accuracy + +
+ )} +
+ )} +
+
+
+ {selectedFileData === 'FAILED_TO_LOAD_FILE' ? ( +

Failed to load file.

+ ) : fileExtension === 'pdf' ? ( + + ) : ( + <> + file setLightBox(true)} + /> + + {lightBox && ( + setLightBox(false)} + > + )} + + )} +
+ +
+
+

+ {totalRequests + ? 'Request: ' + currentRequestIndex + '/' + totalRequests + : 'No Request. Adjust your search criteria to see more results.'} +

+ {currentRequest && + (currentRequest['Is Reviewed'] ? ( + } + color='success' + style={{ padding: '4px 16px' }} + > + Reviewed + + ) : ( + } + color='warning' + style={{ padding: '4px 16px' }} + > + Not Reviewed + + ))} +
+
+
+ + + { + 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 + + } + value={pageIndexToGoto} + onChange={(e) => { + setPageIndexToGoto(parseInt(e.target.value)); + }} + /> +
+
+
+
+
+ {/* + + + */} + {/*
+

+ {totalRequests + ? 'Request: ' + currentRequestIndex + '/' + totalRequests + : 'No Request. Adjust your search criteria to see more results.'} +

+ {currentRequest && + (currentRequest['Is Reviewed'] ? ( + } + color='success' + style={{ padding: '4px 16px' }} + > + Reviewed + + ) : ( + } + color='warning' + style={{ padding: '4px 16px' }} + > + Not Reviewed + + ))} +
+
+
+ + + { + 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 + + } + value={pageIndexToGoto} + onChange={(e) => { + setPageIndexToGoto(parseInt(e.target.value)); + }} + /> +
+
*/} +
+ {dataSource?.map((data) => { + return ( +
+

+ {' '} + {data.key} +

+ + + +
+ ); + })} +
+
+
+ { + setIsModalOpen(false); + reloadFilters(); + }} + onCancel={() => { + setIsModalOpen(false); + }} + > +
+ + { + setFilterDateRange(dateString); + }} + style={{ width: 200 }} + /> + + +
+ + + + + + + + {reason === 'other' && ( + { + setOtherReason(e.target.value); + }} + style={{ + width: 200, + marginTop: 30, + marginBottom: 24, + marginLeft: 10, + }} + /> + )} +
+
+
+ + + {counter_measure_map[reason]} + + { + setOtherSolution(e.target.value); + }} + style={{ + width: 200, + marginBottom: 24, + marginLeft: 10, + }} + /> + )} +
+
+ {/* {totalRequests > 0 && ( +
+ 'editable-row'} + bordered + dataSource={dataSource} + columns={columns} + /> +
+ )} */} +
+ ); +}; + +export default ReviewPage; diff --git a/cope2n-fe/src/routes/useAppRouter.tsx b/cope2n-fe/src/routes/useAppRouter.tsx index 3e1419b..899824f 100644 --- a/cope2n-fe/src/routes/useAppRouter.tsx +++ b/cope2n-fe/src/routes/useAppRouter.tsx @@ -12,6 +12,7 @@ const DashboardPage = React.lazy(() => import('pages/dashboard')); const InferencePage = React.lazy(() => import('pages/inference/index')); const ReviewsPage = React.lazy(() => import('pages/reviews')); +const ReviewsPage2 = React.lazy(() => import('pages/reviews2')); const ReportsPage = React.lazy(() => import('pages/reports')); const ReportDetailPage = React.lazy( () => import('pages/reports/report_detail'), @@ -65,6 +66,11 @@ export function useAppRouter() { path: '/reviews', element: } />, }, + { + path: '/reviews2', + element: } />, + }, + { path: '/users', element: } />, From 04c155120416315e5b8422327f9b5067b0524c52 Mon Sep 17 00:00:00 2001 From: phanphan Date: Fri, 3 May 2024 09:52:15 +0700 Subject: [PATCH 2/3] update --- cope2n-fe/src/pages/reviews2/const.ts | 49 +++ cope2n-fe/src/pages/reviews2/consts.ts | 10 - cope2n-fe/src/pages/reviews2/index.tsx | 484 +++---------------------- 3 files changed, 90 insertions(+), 453 deletions(-) create mode 100644 cope2n-fe/src/pages/reviews2/const.ts delete mode 100644 cope2n-fe/src/pages/reviews2/consts.ts diff --git a/cope2n-fe/src/pages/reviews2/const.ts b/cope2n-fe/src/pages/reviews2/const.ts new file mode 100644 index 0000000..e4b1f1d --- /dev/null +++ b/cope2n-fe/src/pages/reviews2/const.ts @@ -0,0 +1,49 @@ +import { t } from "@lingui/macro"; + +export const counter_measure_map = { + invalid_image: 'Remove this image from the evaluation report', + missing_information: 'Remove this image from the evaluation report', + too_blurry_text: 'Remove this image from the evaluation report', + too_small_text: 'Remove this image from the evaluation report', + ocr_cannot_extract: 'Improve OCR', + wrong_feedback: 'Update revised result and re-calculate accuracy', + handwritten: 'Remove this image from the evaluation report', + other: 'other', +}; + +export const REASON_BAD_QUALITY = [ + { 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: 'wrong_feedback', label: t`Wrong Feedback` }, + { value: 'ocr_cannot_extract', label: t`Ocr cannot extract` }, + { value: 'other', label: t`Other` }, +] + +export const SOLUTION_BAD_QUALITY =[ + { + value: 'Remove this image from the evaluation report', + label: t`Remove this image from the evaluation report`, + }, + { value: 'Improve OCR', label: t`Improve OCR` }, + { + value: 'Update revised result and re-calculate accuracy', + label: t`Update revised result and re-calculate accuracy`, + }, + { value: 'other', label: t`Other` }, +] + +export const SUBSIDIARIES = [ + { 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' }, +] \ No newline at end of file diff --git a/cope2n-fe/src/pages/reviews2/consts.ts b/cope2n-fe/src/pages/reviews2/consts.ts deleted file mode 100644 index 6f5ddcf..0000000 --- a/cope2n-fe/src/pages/reviews2/consts.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const counter_measure_map = { - invalid_image: 'Remove this image from the evaluation report', - missing_information: 'Remove this image from the evaluation report', - too_blurry_text: 'Remove this image from the evaluation report', - too_small_text: 'Remove this image from the evaluation report', - ocr_cannot_extract: 'Improve OCR', - wrong_feedback: 'Update revised result and re-calculate accuracy', - handwritten: 'Remove this image from the evaluation report', - other: 'other', -}; diff --git a/cope2n-fe/src/pages/reviews2/index.tsx b/cope2n-fe/src/pages/reviews2/index.tsx index 6204de3..07394d1 100644 --- a/cope2n-fe/src/pages/reviews2/index.tsx +++ b/cope2n-fe/src/pages/reviews2/index.tsx @@ -3,12 +3,12 @@ import { ArrowRightOutlined, CheckCircleOutlined, ClockCircleFilled, + CopyOutlined, FullscreenExitOutlined, FullscreenOutlined, } from '@ant-design/icons'; import { t } from '@lingui/macro'; import { Viewer } from '@react-pdf-viewer/core'; -import type { GetRef } from 'antd'; import { Button, DatePicker, @@ -20,22 +20,25 @@ import { notification, Select, Spin, - Table, Tag, } from 'antd'; -import React, { useContext, useEffect, useRef, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import Lightbox from 'react-awesome-lightbox'; import 'react-awesome-lightbox/build/style.css'; import { useHotkeys } from 'react-hotkeys-hook'; import { baseURL } from 'request/api'; -import styled from 'styled-components'; // Import the styles import '@react-pdf-viewer/core/lib/styles/index.css'; import { badQualityReasonSubmit } from 'request'; import { normalizeData } from 'utils/field-value-process'; import { fetchAllRequests, fetchRequest } from './api'; -import { counter_measure_map } from './consts'; +import { + counter_measure_map, + REASON_BAD_QUALITY, + SOLUTION_BAD_QUALITY, + SUBSIDIARIES, +} from './const'; import FileCard from './FileCard'; const siderStyle: React.CSSProperties = { @@ -44,106 +47,6 @@ const siderStyle: React.CSSProperties = { flexBasis: 400, flexShrink: 0, }; - -const StyledTable = styled(Table)` - & .sbt-table-cell { - padding: 4px !important; - } -`; - -type InputRef = GetRef; -type FormInstance = GetRef>; - -const EditableContext = React.createContext | null>(null); - -interface Item { - key: string; - accuracy: number; - submitted: string; - revised: string; - action: string; -} - -interface EditableRowProps { - index: number; -} - -const EditableRow: React.FC = ({ index, ...props }) => { - const [form] = Form.useForm(); - return ( - - - - - - ); -}; - -interface EditableCellProps { - title: React.ReactNode; - editable: boolean; - children: React.ReactNode; - dataIndex: keyof Item; - record: Item; - handleSave: (record: Item) => void; -} - -const EditableCell: React.FC = ({ - title, - editable, - children, - dataIndex, - record, - handleSave, - ...restProps -}) => { - const [editing, setEditing] = useState(false); - const inputRef = useRef(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 ? ( - - - - ) : ( -
- {children} -
- ); - } - - return {childNode}; -}; - // type EditableTableProps = Parameters[0]; const ReviewPage = () => { @@ -324,13 +227,6 @@ const ReviewPage = () => { }); }, []); - const components = { - body: { - row: EditableRow, - cell: EditableCell, - }, - }; - // "Key", "Accuracy", "Submitted", "Revised" interface DataType { key: string; @@ -407,159 +303,6 @@ const ReviewPage = () => { }); }; - const defaultColumns = [ - { - title: 'Key', - dataIndex: 'key', - key: 'key', - width: 200, - }, - { - title: 'Predicted', - dataIndex: 'predicted', - key: 'predicted', - render: (text) => { - if (!text) return {''}; - const displayedContent = text; - if (typeof displayedContent === 'string') { - return {displayedContent}; - } else if (typeof displayedContent === 'object') { - if (displayedContent.length === 0) { - return {''}; - } - // Set all empty values to "" - for (const key in displayedContent) { - if (!displayedContent[key]) { - displayedContent[key] = ''; - } - } - return ( - - {displayedContent.join(', ')} - - ); - } - return {displayedContent}; - }, - }, - { - title: 'Submitted', - dataIndex: 'submitted', - key: 'submitted', - render: (text) => { - if (!text) return {''}; - const displayedContent = text; - if (typeof displayedContent === 'string') { - return {displayedContent}; - } else if (typeof displayedContent === 'object') { - if (displayedContent.length === 0) { - return {''}; - } - // Set all empty values to "" - for (const key in displayedContent) { - if (!displayedContent[key]) { - displayedContent[key] = ''; - } - } - return ( - - {displayedContent.join(', ')} - - ); - } - return {displayedContent}; - }, - }, - { - title: ( -
- Revised   - - -
- ), - dataIndex: 'revised', - key: 'revised', - editable: true, - render: (text) => { - if (!text) return {''}; - const displayedContent = text; - if (typeof displayedContent === 'string') { - return {displayedContent}; - } else if (typeof displayedContent === 'object') { - if (displayedContent.length === 0) { - return {''}; - } - // Set all empty values to "" - for (const key in displayedContent) { - if (!displayedContent[key]) { - displayedContent[key] = ''; - } - } - return ( - - {displayedContent.join(', ')} - - ); - } - return {displayedContent}; - }, - }, - ]; - - 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); @@ -642,6 +385,7 @@ const ReviewPage = () => {
{ />
- {/* - - - */} - {/*
-

- {totalRequests - ? 'Request: ' + currentRequestIndex + '/' + totalRequests - : 'No Request. Adjust your search criteria to see more results.'} -

- {currentRequest && - (currentRequest['Is Reviewed'] ? ( - } - color='success' - style={{ padding: '4px 16px' }} - > - Reviewed - - ) : ( - } - color='warning' - style={{ padding: '4px 16px' }} - > - Not Reviewed - - ))} -
-
-
- - - { - 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 - - } - value={pageIndexToGoto} - onChange={(e) => { - setPageIndexToGoto(parseInt(e.target.value)); - }} - /> -
-
*/}
{dataSource?.map((data) => { return (
-

- {' '} - {data.key} -

+
+

+ {data.key} +

+
{ @@ -1337,18 +962,7 @@ const ReviewPage = () => { Redemption { : '' } /> - Uploaded date + Created date Request time { Processing time { Raw accuracy @@ -536,13 +530,14 @@ const ReviewPage = () => {
@@ -551,36 +546,40 @@ const ReviewPage = () => { flexGrow: 1, textAlign: 'center', overflow: 'auto', - padding: '4px', }} > - {selectedFileData === 'FAILED_TO_LOAD_FILE' ? ( -

Failed to load file.

- ) : fileExtension === 'pdf' ? ( - - ) : ( - <> - file setLightBox(true)} - /> + + {selectedFileData === 'FAILED_TO_LOAD_FILE' ? ( +

Failed to load file.

+ ) : fileExtension === 'pdf' ? ( + + ) : ( + <> + file setLightBox(true)} + onLoad={() => { + setImageLoading(false); + }} + /> - {lightBox && ( - setLightBox(false)} - > - )} - - )} + {lightBox && ( + setLightBox(false)} + > + )} + + )} +
{ flexGrow: 0, display: 'flex', justifyContent: 'space-between', + margin: '8px 0 0', alignItems: 'center', }} > @@ -596,7 +596,6 @@ const ReviewPage = () => { display: 'flex', justifyContent: 'center', alignItems: 'center', - margin: '8px 0 4px', }} >

@@ -688,18 +687,33 @@ const ReviewPage = () => {

-
-
+
+
{dataSource?.map((data) => { return (
-
-

- {data.key} -

+
+

{data.key}

+
{ if (["imei_number", "purchase_date"].includes(key) && typeof(value) === "string") { value = value.split(","); } + if(key === 'imei_number' && value === null){ + value = [null] + } if (typeof (value) === "object" && value?.length > 0) { for (let i = 0; i < value.length; i++) { value[i] = normalizeData("text", value[i]);