From 2c84db5ffd5860c9b3f841267c5ebcea6e08aba6 Mon Sep 17 00:00:00 2001 From: arealclimber Date: Thu, 15 Aug 2024 16:39:09 +0800 Subject: [PATCH 1/4] feat: pending ocr list to reveal immediately after user uploads file, and transform the pending ocr to uploaded ocr after successful post response, yet wait for the backend modification #2057 #2075 --- package.json | 4 +- .../journal_upload_area.tsx | 53 +++++++++++++----- src/components/step_one_tab/step_one_tab.tsx | 32 +++++++++-- .../uploaded_file_item/uploaded_file_item.tsx | 14 +++-- src/contexts/accounting_context.tsx | 54 ++++++++++++++++++- src/interfaces/accounting_account.ts | 2 +- src/interfaces/ocr.ts | 5 +- 7 files changed, 138 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 8d83c2ddc..e2b65616b 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "react-usestateref": "^1.0.8", "sharp": "^0.33.3", "tailwind-merge": "^2.2.2", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "uuid": "^10.0.0" }, "devDependencies": { "@babel/eslint-plugin": "^7.23.5", @@ -67,6 +68,7 @@ "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", + "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^6.16.0", "@typescript-eslint/parser": "^6.16.0", "autoprefixer": "^10.4.16", diff --git a/src/components/journal_upload_area/journal_upload_area.tsx b/src/components/journal_upload_area/journal_upload_area.tsx index 609f50e04..272cf9237 100644 --- a/src/components/journal_upload_area/journal_upload_area.tsx +++ b/src/components/journal_upload_area/journal_upload_area.tsx @@ -1,16 +1,18 @@ +import { v4 as uuidv4 } from 'uuid'; import React, { useState, useEffect } from 'react'; import { useTranslation } from 'next-i18next'; import Image from 'next/image'; import APIHandler from '@/lib/utils/api_handler'; import { APIName } from '@/constants/api_connection'; -import { IAccountResultStatus } from '@/interfaces/accounting_account'; +// import { IAccountResultStatus } from '@/interfaces/accounting_account'; import { useUserCtx } from '@/contexts/user_context'; import { useGlobalCtx } from '@/contexts/global_context'; import { useAccountingCtx } from '@/contexts/accounting_context'; import { ProgressStatus } from '@/constants/account'; import { MessageType } from '@/interfaces/message_modal'; import { ToastType } from '@/interfaces/toastify'; -import { getTimestampNow } from '@/lib/utils/common'; +import { transformBytesToFileSizeString } from '@/lib/utils/common'; +import { IOCR } from '@/interfaces/ocr'; interface FileInfo { file: File; @@ -21,7 +23,8 @@ interface FileInfo { const JournalUploadArea = () => { const { t } = useTranslation('common'); const { selectedCompany } = useUserCtx(); - const { setInvoiceIdHandler, addOCRHandler } = useAccountingCtx(); + const { setInvoiceIdHandler, addPendingOCRHandler, deletePendingOCRHandler, addOCRHandler } = + useAccountingCtx(); const { messageModalDataHandler, messageModalVisibilityHandler, toastHandler } = useGlobalCtx(); const { @@ -29,7 +32,8 @@ const JournalUploadArea = () => { data: results, success: uploadSuccess, code: uploadCode, - } = APIHandler(APIName.OCR_UPLOAD); + } = APIHandler(APIName.OCR_UPLOAD); + // IAccountResultStatus[] // Info: (20240711 - Julian) 上傳的檔案 const [uploadFile, setUploadFile] = useState(null); @@ -44,10 +48,11 @@ const JournalUploadArea = () => { const { files } = event.target; if (files && files.length > 0) { const file = files[0]; + const fileSize = transformBytesToFileSizeString(file.size); setUploadFile({ file, name: file.name, - size: file.size.toString(), + size: fileSize, }); } }; @@ -65,10 +70,11 @@ const JournalUploadArea = () => { event.preventDefault(); const droppedFile = event.dataTransfer.files[0]; if (droppedFile) { + const fileSize = transformBytesToFileSizeString(droppedFile.size); setUploadFile({ file: droppedFile, name: droppedFile.name, - size: droppedFile.size.toString(), + size: fileSize, }); setIsDragOver(false); } @@ -77,11 +83,19 @@ const JournalUploadArea = () => { useEffect(() => { if (uploadFile && selectedCompany) { const formData = new FormData(); + const uuid = uuidv4(); formData.append('image', uploadFile.file); + // TODO: in dev (20240815 - Shirley) 加上 imageSize, imageName, uploadIdentifier + // formData.append('imageSize', uploadFile.size); + // formData.append('imageName', uploadFile.name); + // formData.append('uploadIdentifier', uuid); + // eslint-disable-next-line no-console + // console.log('formData', formData); // Info: (20240711 - Julian) 點擊上傳後才升起 flag // setIsShowSuccessModal(true); - addOCRHandler(`${getTimestampNow()}`, uploadFile.name, uploadFile.size); + // addOCRHandler(`${getTimestampNow()}`, uploadFile.name, uploadFile.size, uuid); + addPendingOCRHandler(uploadFile.name, uploadFile.size, uuid); uploadInvoice({ params: { companyId: selectedCompany.id }, body: formData }); } @@ -90,7 +104,7 @@ const JournalUploadArea = () => { useEffect(() => { if (uploadSuccess && results) { results.forEach((result) => { - const { resultId } = result; + // const { resultId } = result; /* Info: (20240805 - Anna) 將狀態的翻譯key值存到變數 */ const translatedStatus = t( `PROGRESS_STATUS.${result.status.toUpperCase().replace(/_/g, '_')}` @@ -107,10 +121,25 @@ const JournalUploadArea = () => { closeable: true, type: ToastType.SUCCESS, }); - setInvoiceIdHandler(resultId); - // if (uploadFile) { - // addOCRHandler(resultId, uploadFile.name, uploadFile.size); - // } + setInvoiceIdHandler(result.aichResultId); + // setInvoiceIdHandler(resultId); + // TODO: in dev (20240815 - Shirley) 加上 + // eslint-disable-next-line no-console + console.log('result in JournalUploadArea', result); + if ( + result?.uploadIdentifier && + result?.aichResultId && + result?.imageName && + result?.imageSize + ) { + addOCRHandler( + result.aichResultId, + result.imageName, + result.imageSize, + result.uploadIdentifier + ); + deletePendingOCRHandler(result?.uploadIdentifier); + } // messageModalDataHandler({ // // title: 'Upload Successful', // title: t('JOURNAL.UPLOAD_SUCCESSFUL'), diff --git a/src/components/step_one_tab/step_one_tab.tsx b/src/components/step_one_tab/step_one_tab.tsx index 4956765da..a9f2139cd 100644 --- a/src/components/step_one_tab/step_one_tab.tsx +++ b/src/components/step_one_tab/step_one_tab.tsx @@ -33,12 +33,19 @@ const StepOneTab = () => { const { t } = useTranslation('common'); const { cameraScannerVisibilityHandler, toastHandler } = useGlobalCtx(); const { selectedCompany } = useUserCtx(); - const { OCRList, OCRListStatus, updateOCRListHandler, selectOCRHandler, deleteOCRHandler } = - useAccountingCtx(); + const { + OCRList, + OCRListStatus, + updateOCRListHandler, + selectOCRHandler, + deleteOCRHandler, + pendingOCRList, + } = useAccountingCtx(); // Info: (20240809 - Shirley) disabled for now , 分頁功能在 alpha release 還沒實作 // eslint-disable-next-line @typescript-eslint/no-unused-vars const [currentFilePage, setCurrentFilePage] = useState(1); const [fileList, setFileList] = useState(OCRList); + // const [pendingFileList, setPendingFileList] = useState(pendingOCRList); // Info: (20240809 - Shirley) disabled for now , 分頁功能在 alpha release 還沒實作 // eslint-disable-next-line @typescript-eslint/no-unused-vars const [totalPages, setTotalPages] = useState(1); @@ -83,6 +90,9 @@ const StepOneTab = () => { } }; + // eslint-disable-next-line no-console + console.log('in StepOneTab, fileList', fileList, 'pendingOCRList', pendingOCRList); + useEffect(() => { setTotalPages(Math.ceil(fileList.length / 5)); }, [fileList]); @@ -154,6 +164,16 @@ const StepOneTab = () => { }; const displayedFileList = fileList.map((data) => ( + + )); + + const displayedPendingFileList = pendingOCRList.map((data) => ( { )); const uploadedFileSection = - fileList.length > 0 ? ( + fileList.length > 0 || pendingOCRList.length > 0 ? ( <>

@@ -182,7 +202,11 @@ const StepOneTab = () => {
{/* Info: (20240523 - Julian) Uploaded File List */}
-
{displayedFileList}
+
+ {displayedFileList} + {displayedPendingFileList} +
+ {/* Info: (20240523 - Julian) Pagination */} {/* Info: (20240809 - Shirley) disabled for now , 分頁功能在 alpha release 還沒實作 */} {/* {totalPages > 1 && ( diff --git a/src/components/uploaded_file_item/uploaded_file_item.tsx b/src/components/uploaded_file_item/uploaded_file_item.tsx index 8a7fa4ec1..5eb95eb6a 100644 --- a/src/components/uploaded_file_item/uploaded_file_item.tsx +++ b/src/components/uploaded_file_item/uploaded_file_item.tsx @@ -53,9 +53,9 @@ const UploadedFileItem = ({ clickHandler(itemData); }; - // Info: (20240527 - Julian) 若檔名過長,則擷取前 3 個和後 4 個(副檔名)字元,中間以 ... 代替 + // Info: (20240527 - Julian) 若檔名過長,則擷取前 5 個和後 4 個(副檔名)字元,中間以 ... 代替 const truncatedFileName = - imageName.length > 20 ? `${imageName.slice(0, 3)}...${imageName.slice(-4)}` : imageName; + imageName.length > 20 ? `${imageName.slice(0, 5)}...${imageName.slice(-4)}` : imageName; const displayedPauseButton = status === ProgressStatus.PAUSED ? : ; @@ -81,8 +81,14 @@ const UploadedFileItem = ({ {/* Info: 掃描動畫 (20240814 - Shirley) */} {/* Info: 文件縮略圖 (20240814 - Shirley) */}
- {/* Info: 文件名 (20240814 - Shirley) */} - {/* Info: 文件大小 (20240814 - Shirley) */} +

+ {truncatedFileName} +

+

+ {imageSize} +

{/* Info: 狀態圖標 (20240814 - Shirley) */} diff --git a/src/contexts/accounting_context.tsx b/src/contexts/accounting_context.tsx index f43497549..b2bc66f35 100644 --- a/src/contexts/accounting_context.tsx +++ b/src/contexts/accounting_context.tsx @@ -57,8 +57,15 @@ interface IAccountingContext { OCRList: IOCR[]; OCRListStatus: { listSuccess: boolean | undefined; listCode: string | undefined }; updateOCRListHandler: (companyId: number | undefined, update: boolean) => void; - addOCRHandler: (aichId: string, imageName: string, imageSize: string) => void; + addOCRHandler: ( + aichId: string, + imageName: string, + imageSize: string, + uploadIdentifier: string + ) => void; deleteOCRHandler: (aichId: string) => void; + addPendingOCRHandler: (imageName: string, imageSize: string, uploadIdentifier: string) => void; + deletePendingOCRHandler: (uploadIdentifier: string) => void; accountList: IAccount[]; getAccountListHandler: ( companyId: number, @@ -114,6 +121,7 @@ interface IAccountingContext { generateAccountTitle: (account: IAccount | null) => string; deleteOwnAccountTitle: (companyId: number, id: number) => void; + pendingOCRList: IOCR[]; } const initialAccountingContext: IAccountingContext = { @@ -127,6 +135,8 @@ const initialAccountingContext: IAccountingContext = { updateOCRListHandler: () => {}, addOCRHandler: () => {}, deleteOCRHandler: () => {}, + addPendingOCRHandler: () => {}, + deletePendingOCRHandler: () => {}, accountList: [], getAccountListHandler: () => {}, getAIStatusHandler: () => {}, @@ -159,6 +169,7 @@ const initialAccountingContext: IAccountingContext = { generateAccountTitle: () => 'Account Title', deleteOwnAccountTitle: () => {}, + pendingOCRList: [], }; export const AccountingContext = createContext(initialAccountingContext); @@ -212,6 +223,9 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { const [intervalId, setIntervalId] = useState(null); const [inputDescription, setInputDescription] = useState(''); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [pendingOCRList, setPendingOCRList] = useState([]); + const getAccountListHandler = ( companyId: number, type?: string, @@ -297,7 +311,12 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { }); }; - const addOCRHandler = (aichId: string, imageName: string, imageSize: string) => { + const addOCRHandler = ( + aichId: string, + imageName: string, + imageSize: string, + uploadIdentifier: string + ) => { const now = getTimestampNow(); const pendingOCR: IOCR = { id: now, @@ -308,6 +327,7 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { progress: 0, status: ProgressStatus.WAITING_FOR_UPLOAD, createdAt: 0, + uploadIdentifier, }; setOCRList((prev) => { const rs = [...prev, pendingOCR]; @@ -315,6 +335,31 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { }); }; + const addPendingOCRHandler = (imageName: string, imageSize: string, uploadIdentifier: string) => { + const ocr: IOCR = { + id: getTimestampNow(), + aichResultId: getTimestampNow().toString(), + imageName, + imageUrl: '', + progress: 0, + status: ProgressStatus.WAITING_FOR_UPLOAD, + imageSize, + createdAt: 0, + uploadIdentifier, + }; + setPendingOCRList((prev) => { + const rs = [...prev, ocr]; + return rs; + }); + }; + + const deletePendingOCRHandler = (uploadIdentifier: string) => { + setPendingOCRList((prev) => { + const rs = prev.filter((ocr) => ocr.uploadIdentifier !== uploadIdentifier); + return rs; + }); + }; + useEffect(() => { if (accountSuccess && accountTitleList) { setAccountList(accountTitleList.data); @@ -598,6 +643,8 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { updateOCRListHandler, addOCRHandler, deleteOCRHandler, + addPendingOCRHandler, + deletePendingOCRHandler, accountList, getAccountListHandler, getAIStatusHandler, @@ -628,6 +675,8 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { deleteOwnAccountTitle, inputDescription, inputDescriptionHandler, + + pendingOCRList, }), [ OCRList, @@ -652,6 +701,7 @@ export const AccountingProvider = ({ children }: IAccountingProvider) => { selectedJournal, selectJournalHandler, inputDescription, + pendingOCRList, ] ); diff --git a/src/interfaces/accounting_account.ts b/src/interfaces/accounting_account.ts index 87428867d..dd6b24a38 100644 --- a/src/interfaces/accounting_account.ts +++ b/src/interfaces/accounting_account.ts @@ -1,7 +1,7 @@ import { AccountType, EquityType, ProgressStatus } from '@/constants/account'; import { Account } from '@prisma/client'; import { ReportSheetType } from '@/constants/report'; -import { IPaginatedData } from './pagination'; +import { IPaginatedData } from '@/interfaces/pagination'; export interface IAccount { id: number; diff --git a/src/interfaces/ocr.ts b/src/interfaces/ocr.ts index e84a2636d..70c416f21 100644 --- a/src/interfaces/ocr.ts +++ b/src/interfaces/ocr.ts @@ -3,10 +3,11 @@ import { ProgressStatus } from '@/constants/account'; export interface IOCR { id: number; aichResultId: string; - imageName: string; + imageName: string; // Info: from frontend (20240815 - Shirley) imageUrl: string; - imageSize: string; + imageSize: string; // Info: from frontend (20240815 - Shirley) progress: number; // 0 ~ 100 Float status: ProgressStatus; createdAt: number; + uploadIdentifier?: string; // Info: from frontend (20240815 - Shirley) } From 5eefaa4f5c28560bc9dbc8b125efa3729ae4407a Mon Sep 17 00:00:00 2001 From: arealclimber Date: Thu, 15 Aug 2024 16:46:00 +0800 Subject: [PATCH 2/4] refactor: send imageName, imageSize, uploadIdentifier as body to the OCR POST API #2057 #2075 --- package.json | 2 +- src/components/journal_upload_area/journal_upload_area.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e2b65616b..9cb3653aa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.0+7", + "version": "0.8.0+8", "private": false, "scripts": { "dev": "next dev", diff --git a/src/components/journal_upload_area/journal_upload_area.tsx b/src/components/journal_upload_area/journal_upload_area.tsx index 272cf9237..920dd4d33 100644 --- a/src/components/journal_upload_area/journal_upload_area.tsx +++ b/src/components/journal_upload_area/journal_upload_area.tsx @@ -86,9 +86,9 @@ const JournalUploadArea = () => { const uuid = uuidv4(); formData.append('image', uploadFile.file); // TODO: in dev (20240815 - Shirley) 加上 imageSize, imageName, uploadIdentifier - // formData.append('imageSize', uploadFile.size); - // formData.append('imageName', uploadFile.name); - // formData.append('uploadIdentifier', uuid); + formData.append('imageSize', uploadFile.size); + formData.append('imageName', uploadFile.name); + formData.append('uploadIdentifier', uuid); // eslint-disable-next-line no-console // console.log('formData', formData); From d4bde28eece774e49cc6a80c8350ad411536c0ff Mon Sep 17 00:00:00 2001 From: RexBearIU Date: Thu, 15 Aug 2024 17:43:27 +0800 Subject: [PATCH 3/4] fix: report formatter fixed --- package.json | 2 +- src/lib/utils/formatter/report.formatter.ts | 12 ++-------- src/lib/utils/repo/report.repo.ts | 3 +-- .../[companyId]/report/[reportId]/index.ts | 22 +++++++++++-------- 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 8d83c2ddc..b2693f385 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.0+7", + "version": "0.8.0+8", "private": false, "scripts": { "dev": "next dev", diff --git a/src/lib/utils/formatter/report.formatter.ts b/src/lib/utils/formatter/report.formatter.ts index fed53df1b..c6849a8dd 100644 --- a/src/lib/utils/formatter/report.formatter.ts +++ b/src/lib/utils/formatter/report.formatter.ts @@ -1,8 +1,6 @@ import { ReportSheetType, ReportType } from '@/constants/report'; import { IPaginatedReport, IReport, IReportIncludeCompanyProject } from '@/interfaces/report'; import { isReportSheetType, isReportType } from '@/lib/utils/type_guard/report'; -import { IAccountReadyForFrontend } from '@/interfaces/accounting_account'; -import { isIAccountReadyForFrontendArray } from '@/lib/utils/type_guard/account'; export function formatIReport(report: IReportIncludeCompanyProject): IReport { const type: ReportType = isReportType(report.reportType) @@ -12,14 +10,8 @@ export function formatIReport(report: IReportIncludeCompanyProject): IReport { ? report.reportType : ReportSheetType.BALANCE_SHEET; const reportContent = JSON.parse(report.content as string); - - // Info: (20240729 - Murky) Bad code, not robust - const content: IAccountReadyForFrontend[] = isIAccountReadyForFrontendArray(reportContent.content) - ? reportContent.content - : []; - + const { content, otherInfo, ...rest } = reportContent; // Info: (20240729 - Murky) Bad code, not robust - const otherInfo = reportContent.otherInfo || {}; const project = report.project ? { id: report.project.id.toString(), @@ -47,7 +39,7 @@ export function formatIReport(report: IReportIncludeCompanyProject): IReport { downloadLink: report.downloadLink || '', blockChainExplorerLink: report.blockChainExplorerLink || '', evidenceId: report.evidenceId || '', - content, + content: content ?? rest, otherInfo, createdAt: report.createdAt, updatedAt: report.updatedAt, diff --git a/src/lib/utils/repo/report.repo.ts b/src/lib/utils/repo/report.repo.ts index 076a49c64..2a658ecc8 100644 --- a/src/lib/utils/repo/report.repo.ts +++ b/src/lib/utils/repo/report.repo.ts @@ -34,14 +34,13 @@ export async function findFirstReportByFromTo( return report; } -export async function findUniqueReportById(companyId: number, reportId: number) { +export async function findUniqueReportById(reportId: number) { let report: IReportIncludeCompanyProject | null = null; try { report = await prisma.report.findUnique({ where: { id: reportId, - companyId, OR: [{ deletedAt: 0 }, { deletedAt: null }], }, include: { diff --git a/src/pages/api/v1/company/[companyId]/report/[reportId]/index.ts b/src/pages/api/v1/company/[companyId]/report/[reportId]/index.ts index a04885e0c..c0d06da62 100644 --- a/src/pages/api/v1/company/[companyId]/report/[reportId]/index.ts +++ b/src/pages/api/v1/company/[companyId]/report/[reportId]/index.ts @@ -49,8 +49,8 @@ export function getReportTypeFromReport(report: IReport | null) { return reportType; } -export async function getPeriodReport(companyId: number, reportId: number) { - const curPeriodReportFromDB = await findUniqueReportById(companyId, reportId); +export async function getPeriodReport(reportId: number) { + const curPeriodReportFromDB = await findUniqueReportById(reportId); let curPeriodReport: IReport | null = null; let company: Company | null = null; if (curPeriodReportFromDB) { @@ -209,16 +209,17 @@ export function formatPayloadFromIReport(report: IReport, company: Company): Fin export async function handleGETRequest( companyId: number, req: NextApiRequest -): Promise { +): Promise { const { reportIdNumber } = formatGetRequestQueryParams(req); let payload = null; if (reportIdNumber !== null) { - const { curPeriodReport, company } = await getPeriodReport(companyId, reportIdNumber); - - if (curPeriodReport && company) { + const { curPeriodReport, company } = await getPeriodReport(reportIdNumber); + if (curPeriodReport && company && curPeriodReport.reportType !== ReportSheetType.REPORT_401) { payload = formatPayloadFromIReport(curPeriodReport, company); + } else { + payload = curPeriodReport; } } @@ -227,10 +228,10 @@ export async function handleGETRequest( export default async function handler( req: NextApiRequest, - res: NextApiResponse> + res: NextApiResponse> ) { let statusMessage: string = STATUS_MESSAGE.BAD_REQUEST; - let payload: FinancialReport | null = null; + let payload: FinancialReport | IReport | null = null; try { const session = await getSession(req, res); const { userId, companyId } = session; @@ -258,6 +259,9 @@ export default async function handler( console.log(error); statusMessage = error.message; } - const { httpCode, result } = formatApiResponse(statusMessage, payload); + const { httpCode, result } = formatApiResponse( + statusMessage, + payload + ); res.status(httpCode).json(result); } From fecf870810eb4f9eb4551aec3ca970cdd89e8b3c Mon Sep 17 00:00:00 2001 From: Luphia Chang Date: Thu, 15 Aug 2024 20:28:17 +0800 Subject: [PATCH 4/4] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b2693f385..e946ef1a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.0+8", + "version": "0.8.0+9", "private": false, "scripts": { "dev": "next dev",