diff --git a/package.json b/package.json index c637fe216..45349ca1b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+34", + "version": "0.8.2+35", "private": false, "scripts": { "dev": "next dev", diff --git a/src/components/certificate/certificate_qrcode_modal.tsx b/src/components/certificate/certificate_qrcode_modal.tsx index cb163802b..49fffb986 100644 --- a/src/components/certificate/certificate_qrcode_modal.tsx +++ b/src/components/certificate/certificate_qrcode_modal.tsx @@ -40,7 +40,7 @@ const CertificateQRCodeModal: React.FC = ({
Url
for mobile upload
-
+
{/* Info: (20240924 - tzuhan) 發票縮略圖 */} = ({ }, }} /> + {`${isDev ? 'http://localhost:3000' : DOMAIN}/${ISUNFA_ROUTE.UPLOAD}?token=${token}`}
@@ -127,8 +180,8 @@ const MobileUploadPage: React.FC = () => {

Uploaded Certificates

{uploadedCertificates.map((certificate, index) => ( -
- {certificate.status !== ProgressStatus.SYSTEM_ERROR ? ( +
+ {certificate.status !== ProgressStatus.FAILED ? ( {`Uploaded { ); }; -const getStaticPropsFunction = async ({ locale }: ILocale) => ({ +export const getStaticProps = async ({ locale }: ILocale) => ({ props: { ...(await serverSideTranslations(locale, [ 'common', @@ -163,6 +216,4 @@ const getStaticPropsFunction = async ({ locale }: ILocale) => ({ }, }); -export const getStaticProps = getStaticPropsFunction; - export default MobileUploadPage; diff --git a/src/pages/certificate_list.tsx b/src/pages/users/accounting/certificate_list.tsx similarity index 76% rename from src/pages/certificate_list.tsx rename to src/pages/users/accounting/certificate_list.tsx index 7191ff249..a9604fdc6 100644 --- a/src/pages/certificate_list.tsx +++ b/src/pages/users/accounting/certificate_list.tsx @@ -18,14 +18,19 @@ import { } from '@/interfaces/certificate'; import Certificate from '@/components/certificate/certificate'; import CertificateEditModal from '@/components/certificate/certificate_edit_modal'; -import Pusher, { Channel } from 'pusher-js'; +import { getPusherInstance } from '@/lib/pusherClient'; import FloatingUploadPopup from '@/components/floating_upload_popup/floating_upload_popup'; import CertificateQRCodeModal from '@/components/certificate/certificate_qrcode_modal'; -import Image from 'next/image'; -import { v4 as uuidv4 } from 'uuid'; -import { ProgressStatus } from '@/constants/account'; +import APIHandler from '@/lib/utils/api_handler'; +import { CERTIFICATE_EVENT, PRIVATE_CHANNEL } from '@/constants/pusher'; +import useStateRef from 'react-usestateref'; +import { useUserCtx } from '@/contexts/user_context'; -const UploadCertificatePage: React.FC = () => { +const CertificateListPage: React.FC = () => { + const { selectedCompany } = useUserCtx(); + const { id: companyId } = selectedCompany!; + const { trigger: encryptAPI } = APIHandler(APIName.ENCRYPT); + const [token, setToken, tokenRef] = useStateRef(undefined); const [showQRCode, setShowQRCode] = useState(false); const [activeTab, setActiveTab] = useState(0); const [data, setData] = useState<{ [tab: number]: { [id: number]: ICertificateUI } }>({ @@ -43,8 +48,10 @@ const UploadCertificatePage: React.FC = () => { 0: false, 1: false, }); - const token = uuidv4(); // Info: (20241007 - tzuhan) 生成唯一 token - const [uploadingCertificates, setUploadingCertificates] = useState([]); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [uploadingCertificates, setUploadingCertificates, uploadingCertificatesRef] = useStateRef<{ + [id: number]: ICertificateInfo; + }>({}); const handleApiResponse = useCallback((resData: ICertificate[]) => { const sumInvoiceTotalPrice = { @@ -189,61 +196,60 @@ const UploadCertificatePage: React.FC = () => { console.log('Save selected id:', certificate); }, []); - const certificateHandler = async (certificateData: { url: string; token: string }) => { - // TODO: (20241007 - tzuhan) post certificate data to server and get uploading certificate list back and update certificate list when uploaded - // TODO: (20241007 - tzuhan) get Token from server - if (certificateData.token === token) { - // Info: (20241007 - tzuhan) 使用 fetch 下載圖片文件 - const response = await fetch(certificateData.url); - const blob = await response.blob(); + const certificateHandler = useCallback( + async (message: { token: string; certificate: ICertificateInfo }) => { + const { token: receivedToken, certificate: certificateData } = message; + // Deprecated: (20241011 - tzuhan) Debugging purpose + // eslint-disable-next-line no-console + console.log( + `pusher got message, (token(${tokenRef.current})===_token${receivedToken})?${tokenRef.current === receivedToken} message:`, + message + ); - // Info: (20241007 - tzuhan) 獲取文件名,從 response headers 提取 - const contentDisposition = response.headers.get('Content-Disposition'); - let fileName = 'unknown'; - if (contentDisposition) { - const match = contentDisposition.match(/filename="(.+)"/); - if (match && match[1]) { - // Info: (20241007 - tzuhan) 使用陣列解構提取文件名 - [, fileName] = match; - } + if (receivedToken === tokenRef.current) { + const updatedCertificates = { + ...uploadingCertificatesRef.current, + }; + updatedCertificates[certificateData.id] = certificateData; + setUploadingCertificates(updatedCertificates); + // Deprecated: (20241011 - tzuhan) Debugging purpose + // eslint-disable-next-line no-console + console.log(`uploadingCertificatesRef.current:`, uploadingCertificatesRef.current); + } + }, + [tokenRef, setUploadingCertificates] + ); + const getToken = useCallback(async () => { + if (!tokenRef.current) { + const res = await encryptAPI({ body: { companyId } }); + if (res.success && res.data) { + setToken(res.data); } else { - // Info: (20241007 - tzuhan) 如果沒有提供 header,可以從 url 推斷出文件名 - fileName = certificateData.url.split('/').pop() || 'unknown'; + setToken(''); } - - // Info: (20241007 - tzuhan) 獲取文件大小 - const fileSize = blob.size; - - const imageObjectUrl = URL.createObjectURL(blob); - setUploadingCertificates((prev) => [ - ...prev, - { - url: imageObjectUrl, - status: ProgressStatus.IN_PROGRESS, - name: fileName, - size: fileSize, - progress: 80, - }, - ]); } - }; + }, [tokenRef, companyId, setToken]); + + const toggleQRCode = useCallback(() => { + getToken(); + setShowQRCode((prev) => !prev); + }, []); useEffect(() => { - const pusher = new Pusher(process.env.NEXT_PUBLIC_PUSHER_KEY!, { - cluster: '', - wsHost: process.env.NEXT_PUBLIC_PUSHER_HOST!, - wsPort: parseFloat(process.env.NEXT_PUBLIC_PUSHER_PORT!), - }); + getToken(); + }, [getToken]); - const channel: Channel = pusher.subscribe('certificate-channel'); + useEffect(() => { + const pusher = getPusherInstance(); + const channel = pusher.subscribe(PRIVATE_CHANNEL.CERTIFICATE); - channel.bind('certificate-event', certificateHandler); + channel.bind(CERTIFICATE_EVENT.UPLOAD, certificateHandler); return () => { - channel.unbind('certificate-event', certificateHandler); - pusher.unsubscribe('certificate-channel'); + channel.unbind(CERTIFICATE_EVENT.UPLOAD, certificateHandler); + pusher.unsubscribe(PRIVATE_CHANNEL.CERTIFICATE); }; - }, []); + }, [certificateHandler]); return ( <> @@ -251,7 +257,7 @@ const UploadCertificatePage: React.FC = () => { - Upload Certificate - iSunFA + Certificate List - iSunFA
{editingId && ( @@ -262,12 +268,12 @@ const UploadCertificatePage: React.FC = () => { onSave={handleSave} /> )} - {showQRCode && ( + {showQRCode && !!token && ( setShowQRCode((prev) => !prev)} isOnTopOfModal={false} - token={token} + token={tokenRef.current!} /> )} @@ -281,20 +287,7 @@ const UploadCertificatePage: React.FC = () => { {/* Info: (20240919 - tzuhan) Main Content */}
{/* Info: (20240919 - tzuhan) Upload Area */} - setShowQRCode((prev) => !prev)} - /> -
- {uploadingCertificates.map((image, index) => ( -
- {`Received -

Status: {image.status}

-
- ))} -
- + {/* Info: (20240919 - tzuhan) Tabs */} { />
{/* Info: (20240926- tzuhan) Floating Upload Popup */} - {uploadingCertificates.length > 0 && ( - - )} +
@@ -375,4 +368,4 @@ const getStaticPropsFunction = async ({ locale }: ILocale) => ({ export const getStaticProps = getStaticPropsFunction; -export default UploadCertificatePage; +export default CertificateListPage;