diff --git a/package.json b/package.json index f86f2fad..1ceab190 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iSunFA", - "version": "0.8.2+11", + "version": "0.8.2+12", "private": false, "scripts": { "dev": "next dev", diff --git a/public/elements/siri.svg b/public/elements/siri.svg new file mode 100644 index 00000000..d1f2816b --- /dev/null +++ b/public/elements/siri.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/ai_analyzer/ai_analyzer.tsx b/src/components/ai_analyzer/ai_analyzer.tsx new file mode 100644 index 00000000..84f4e77d --- /dev/null +++ b/src/components/ai_analyzer/ai_analyzer.tsx @@ -0,0 +1,24 @@ +import Image from 'next/image'; + +interface AIAnalyzerProps {} + +const AIAnalyzer: React.FC = () => { + return ( +
+
+ AI +
+

+ Please select the certificates for AI to generate the voucher for you +

+
+ ); +}; + +export default AIAnalyzer; diff --git a/src/components/certificate/certificate_edit_modal.tsx b/src/components/certificate/certificate_edit_modal.tsx index 8910b9d1..b094a081 100644 --- a/src/components/certificate/certificate_edit_modal.tsx +++ b/src/components/certificate/certificate_edit_modal.tsx @@ -1,13 +1,13 @@ -import { CERTIFICATE_TYPES, ICertificateUI, INVOICE_TYPES } from '@/interfaces/certificate'; import Image from 'next/image'; import React, { useState } from 'react'; -import NumericInput from '@/components/numeric_input/numeric_input'; -import { RxCross1 } from 'react-icons/rx'; -import useOuterClick from '@/lib/hooks/use_outer_click'; import { useTranslation } from 'react-i18next'; import { IoIosArrowDown } from 'react-icons/io'; +import useOuterClick from '@/lib/hooks/use_outer_click'; +import NumericInput from '@/components/numeric_input/numeric_input'; import Toggle from '@/components/toggle/toggle'; +import Modal from '@/components/modal/modal'; import { Button } from '@/components/button/button'; +import { CERTIFICATE_TYPES, ICertificateUI, INVOICE_TYPES } from '@/interfaces/certificate'; interface CertificateEditModalProps { isOpen: boolean; @@ -74,20 +74,8 @@ const CertificateEditModal: React.FC = ({ if (!isOpen) return null; return ( -
-
- {/* Info: (20240924 - tzuhan) 關閉按鈕 */} - - + + <> {/* Info: (20240924 - tzuhan) 模態框標題 */}

{certificate.invoiceName} @@ -324,8 +312,8 @@ const CertificateEditModal: React.FC = ({

- - + + ); }; diff --git a/src/components/certificate/certificate_grid.tsx b/src/components/certificate/certificate_grid.tsx index 270740cc..c883b56c 100644 --- a/src/components/certificate/certificate_grid.tsx +++ b/src/components/certificate/certificate_grid.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { ICertificateUI } from '@/interfaces/certificate'; import CertificateThumbnail from '@/components/certificate/certificate_thumbnail'; -import FloatingUploadPopup from '@/components/upload_certificate/floating_upload_popup'; +import FloatingUploadPopup from '@/components/floating_upload_popup/floating_upload_popup'; interface CertificateGridProps { data: ICertificateUI[]; // Info: (20240923 - tzuhan) 項目列表 diff --git a/src/components/certificate/certificate_selection.tsx b/src/components/certificate/certificate_selection.tsx new file mode 100644 index 00000000..8bdcda72 --- /dev/null +++ b/src/components/certificate/certificate_selection.tsx @@ -0,0 +1,66 @@ +import Image from 'next/image'; +import { Button } from '@/components/button/button'; +import { AiOutlineLeft, AiOutlineRight } from 'react-icons/ai'; +import { FaPlus } from 'react-icons/fa6'; +import { ICertificate } from '@/interfaces/certificate'; + +interface CertificateSelectionProps { + selectedCertificates: ICertificate[]; + setOpenModal: (open: boolean) => void; +} + +const CertificateSelection: React.FC = ({ + selectedCertificates, + setOpenModal, +}: CertificateSelectionProps) => { + return ( +
+
+
+ {selectedCertificates.map((certificate) => ( +
+ AI +

{certificate.invoiceName}

+
+ ))} +
+ +
+
+
+
+

+ Uploaded {selectedCertificates.length} certificates +

+
+ + +
+
+
+ ); +}; + +export default CertificateSelection; diff --git a/src/components/certificate/certificate_selector_modal.tsx b/src/components/certificate/certificate_selector_modal.tsx new file mode 100644 index 00000000..e4552136 --- /dev/null +++ b/src/components/certificate/certificate_selector_modal.tsx @@ -0,0 +1,112 @@ +import React from 'react'; + +import FilterSection from '@/components/filter_section/filter_section'; +import { ICertificate, ICertificateUI } from '@/interfaces/certificate'; +import { APIName } from '@/constants/api_connection'; +import SelectionPannl from '@/components/certificate/selection_pannel'; +import { Button } from '@/components/button/button'; +import { RxCross1 } from 'react-icons/rx'; + +interface CertificateSelectorModalProps { + isOpen: boolean; + onClose: () => void; // Info: (20240924 - tzuhan) 關閉模態框的回調函數 + selectedCertificates: ICertificateUI[]; // Info: (20240926 - tzuhan) 已選擇的證書 + handleSelect: (ids: number[], isSelected: boolean) => void; // Info: (20240926 - tzuhan) 保存數據的回調函數 + certificates: ICertificateUI[]; // Info: (20240926 - tzuhan) 證書列表 + handleApiResponse: (data: ICertificate[]) => void; // Info: (20240926 - tzuhan) 處理 API 回應的回調函數 + openUploaderModal: () => void; // Info: (20240926 - tzuhan) 打開上傳模態框的回調函數 +} + +const CertificateSelectorModal: React.FC = ({ + isOpen, + onClose, + handleSelect, + selectedCertificates, + handleApiResponse, + certificates, + openUploaderModal, +}) => { + // Info: (20240924 - tzuhan) 不顯示模態框時返回 null + if (!isOpen) return null; + + const handleSelectAll = () => { + handleSelect( + certificates.map((item) => item.id), + true + ); + }; + + const handleComfirm = () => { + handleSelect( + selectedCertificates.map((item) => item.id), + true + ); + onClose(); + }; + + return ( +
+
+ {/* Info: (20240924 - tzuhan) 關閉按鈕 */} + + {/* Info: (20240924 - tzuhan) 模態框標題 */} +

Select Certificates

+

+ Choosing the certificates you want to attach with the voucher +

+ +
+
+
+ (Select {selectedCertificates.length}/{certificates.length}) +
+ +
+
+ +
+ + +
+
+
+ ); +}; + +export default CertificateSelectorModal; diff --git a/src/components/certificate/certificate_uoloader_modal.tsx b/src/components/certificate/certificate_uoloader_modal.tsx new file mode 100644 index 00000000..0fb453eb --- /dev/null +++ b/src/components/certificate/certificate_uoloader_modal.tsx @@ -0,0 +1,128 @@ +import React, { useState } from 'react'; + +import { Button } from '@/components/button/button'; +import { RxCross1 } from 'react-icons/rx'; +import UploadArea from '@/components/upload_area/upload_area'; +import { ProgressStatus } from '@/constants/account'; +import UploadFileItem, { UploadFile } from '@/components/upload_certificate/upload_file_item'; +import { GoArrowLeft } from 'react-icons/go'; +import CircularProgressBar from '@/components/certificate/circular_progress_bar'; + +interface CertificateUploaderModalProps { + isOpen: boolean; + onClose: () => void; // Info: (20240924 - tzuhan) 關閉模態框的回調函數 + onBack: () => void; // Info: (20240926 - tzuhan) 返回上一步的回調函數 +} + +const CertificateUploaderModal: React.FC = ({ + isOpen, + onClose, + onBack, +}) => { + const [files, setFiles] = useState([ + { name: 'preline-ui.xls', size: 7, progress: 20, status: ProgressStatus.IN_PROGRESS }, + { name: 'preline-ui.xls', size: 7, progress: 50, status: ProgressStatus.IN_PROGRESS }, + { name: 'preline-ui.xls', size: 7, progress: 80, status: ProgressStatus.IN_PROGRESS }, + ]); + + const updateFileStatus = (prevFiles: UploadFile[], index: number) => + prevFiles.map((file, i) => { + return i === index + ? { + ...file, + status: + file.status === ProgressStatus.PAUSED + ? ProgressStatus.IN_PROGRESS + : ProgressStatus.PAUSED, + } + : file; + }); + + const togglePause = (index: number) => { + setFiles((prevFiles) => updateFileStatus(prevFiles, index)); + }; + + // Info: (20240919 - tzuhan) 刪除上傳文件 + const deleteFile = (index: number) => { + setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index)); + }; + + // Info: (20240924 - tzuhan) 不顯示模態框時返回 null + if (!isOpen) return null; + return ( +
+
+ {/* Info: (20240924 - tzuhan) 關閉按鈕 */} + + + {/* Info: (20240924 - tzuhan) 模態框標題 */} +

Upload Certificates

+

+ Upload the certificates you want to attach with the voucher +

+ +
+
+ {files.length > 0 ? ( + files.map((file, index) => ( + togglePause(index)} + onDelete={() => deleteFile(index)} + withoutImage + withoutBorder + /> + )) + ) : ( +
No files uploading
+ )} +
+
+
+ +
+
+ + +
+
+
+ ); +}; + +export default CertificateUploaderModal; diff --git a/src/components/certificate/circular_progress_bar.tsx b/src/components/certificate/circular_progress_bar.tsx new file mode 100644 index 00000000..608a930d --- /dev/null +++ b/src/components/certificate/circular_progress_bar.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +interface CircularProgressBarProps { + size: number; // 圓形直徑 + progress: number; // 進度百分比 + strokeWidth: number; // 線條寬度 + remainingText: string; // 顯示的剩餘文字 +} + +const CircularProgressBar: React.FC = ({ + size, + progress, + strokeWidth, + remainingText, +}) => { + // 圓的周長 + const radius = (size - strokeWidth) / 2; + const circumference = radius * 2 * Math.PI; + + // 根據進度計算 dashoffset + const dashOffset = circumference - (progress / 100) * circumference; + + return ( +
+ + + + + {remainingText} +
+ ); +}; + +export default CircularProgressBar; diff --git a/src/components/certificate/selection_pannel.tsx b/src/components/certificate/selection_pannel.tsx new file mode 100644 index 00000000..45716a5d --- /dev/null +++ b/src/components/certificate/selection_pannel.tsx @@ -0,0 +1,43 @@ +import { ICertificateUI } from '@/interfaces/certificate'; +import Image from 'next/image'; +import { FaPlus } from 'react-icons/fa6'; + +interface SelectionPannlProps { + certificates: ICertificateUI[]; + handleSelect: (ids: number[], isSelected: boolean) => void; + openUploaderModal: () => void; +} + +const SelectionPannl: React.FC = ({ + certificates, + handleSelect, + openUploaderModal, +}: SelectionPannlProps) => { + return ( +
+
+
+ +
+ {certificates.map((certificate) => ( +
+ AI +

{certificate.invoiceName}

+
+ ))} +
+
+ ); +}; + +export default SelectionPannl; diff --git a/src/components/filter_section/filter_section.tsx b/src/components/filter_section/filter_section.tsx index 6bf9d4d3..117b1833 100644 --- a/src/components/filter_section/filter_section.tsx +++ b/src/components/filter_section/filter_section.tsx @@ -17,8 +17,8 @@ interface FilterSectionProps { sortingOptions?: string[]; sortingByDate?: boolean; onApiResponse?: (data: ICertificate[]) => void; // Info: (20240919 - tzuhan) 回傳 API 回應資料 - viewType: VIEW_TYPES; - viewToggleHandler: (viewType: VIEW_TYPES) => void; + viewType?: VIEW_TYPES; + viewToggleHandler?: (viewType: VIEW_TYPES) => void; } const FilterSection: React.FC = ({ @@ -143,7 +143,9 @@ const FilterSection: React.FC = ({ {/* Info: (20240919 - tzuhan) 顯示風格切換 */} - + {viewType && viewToggleHandler && ( + + )} {/* Info: (20240919 - tzuhan) 排序選項 */} {sortingByDate ? ( diff --git a/src/components/upload_certificate/floating_upload_popup.tsx b/src/components/floating_upload_popup/floating_upload_popup.tsx similarity index 91% rename from src/components/upload_certificate/floating_upload_popup.tsx rename to src/components/floating_upload_popup/floating_upload_popup.tsx index c348c257..7a772852 100644 --- a/src/components/upload_certificate/floating_upload_popup.tsx +++ b/src/components/floating_upload_popup/floating_upload_popup.tsx @@ -1,15 +1,8 @@ import React, { useState } from 'react'; -import UploadFileItem from '@/components/upload_certificate/upload_file_item'; +import UploadFileItem, { UploadFile } from '@/components/upload_certificate/upload_file_item'; import { ProgressStatus } from '@/constants/account'; import Image from 'next/image'; -interface UploadFile { - name: string; - size: number; // Info: (20240919 - tzuhan) 文件大小(KB) - progress: number; // Info: (20240919 - tzuhan) 上傳進度(0-100) - status: ProgressStatus; // Info: (20240919 - tzuhan) 是否暫停 -} - const FloatingUploadPopup: React.FC = () => { const [files, setFiles] = useState([ { name: 'preline-ui.xls', size: 7, progress: 20, status: ProgressStatus.IN_PROGRESS }, diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx new file mode 100644 index 00000000..b575c170 --- /dev/null +++ b/src/components/modal/modal.tsx @@ -0,0 +1,34 @@ +import React, { ReactElement } from 'react'; +import { RxCross1 } from 'react-icons/rx'; + +interface ModalProps { + isOpen: boolean; + onClose: () => void; // Info: (20240924 - tzuhan) 關閉模態框的回調函數 + children: ReactElement; +} + +const Modal: React.FC = ({ isOpen, onClose, children }) => { + // Info: (20240924 - tzuhan) 不顯示模態框時返回 null + if (!isOpen) return null; + + return ( +
+
+ {/* Info: (20240924 - tzuhan) 關閉按鈕 */} + + {children} +
+
+ ); +}; + +export default Modal; diff --git a/src/components/selection_tool_bar/selection_tool_bar.tsx b/src/components/selection_tool_bar/selection_tool_bar.tsx index 71954cd7..57cd67fe 100644 --- a/src/components/selection_tool_bar/selection_tool_bar.tsx +++ b/src/components/selection_tool_bar/selection_tool_bar.tsx @@ -8,16 +8,16 @@ interface SelectionToolbarProps { isSelectable: boolean; // Info: (20240920 - tzuhan) 是否可選擇 onActiveChange: (active: boolean) => void; // Info: (20240920 - tzuhan) 當打開狀態變更時的回調函數 items: ICertificateUI[]; // Info: (20240920 - tzuhan) 項目列表 - itemType: string; - subtitle: string; + itemType?: string; + subtitle?: string; selectedCount: number; // Info: (20240920 - tzuhan) 選中的項目數量 totalCount: number; // Info: (20240920 - tzuhan) 總項目數量 handleSelect: (ids: number[], isSelected: boolean) => void; operations?: ('ADD_VOUCHER' | 'ADD_ASSET' | 'DELETE')[]; // Info: (20240920 - tzuhan) 操作列表 - onAddVoucher: () => void; // Info: (20240920 - tzuhan) 添加新的憑證的回調函數 - onAddAsset: () => void; // Info: (20240920 - tzuhan) 添加新資產的回調函數 - onDelete: () => void; // Info: (20240920 - tzuhan) 添加刪除的回調函數 - onDownload: () => void; // Info: (20240923 - tzuhan) 添加下載的回調函數 + onAddVoucher?: () => void; // Info: (20240920 - tzuhan) 添加新的憑證的回調函數 + onAddAsset?: () => void; // Info: (20240920 - tzuhan) 添加新資產的回調函數 + onDelete?: () => void; // Info: (20240920 - tzuhan) 添加刪除的回調函數 + onDownload?: () => void; // Info: (20240923 - tzuhan) 添加下載的回調函數 } const SelectionToolbar: React.FC = ({ @@ -66,21 +66,23 @@ const SelectionToolbar: React.FC = ({ {active ? (
{/* Info: (20240920 - tzuhan) 左側選擇計數顯示 */} -
+
(Select {selectedCount}/{totalCount})
{/* Info: (20240920 - tzuhan) 中間操作按鈕 */}
- - {operations.includes('ADD_VOUCHER') && ( + {onDownload && ( + + )} + {operations.includes('ADD_VOUCHER') && onAddVoucher && ( )} - {operations.includes('ADD_ASSET') && ( + {operations.includes('ADD_ASSET') && onAddAsset && ( )} - {operations.includes('DELETE') && ( + {operations.includes('DELETE') && onDelete && (