diff --git a/public/elements/tracing.png b/public/elements/tracking.png similarity index 100% rename from public/elements/tracing.png rename to public/elements/tracking.png diff --git a/public/elements/tracing_tool.png b/public/elements/tracking_tool.png similarity index 100% rename from public/elements/tracing_tool.png rename to public/elements/tracking_tool.png diff --git a/public/elements/tracing_tool_2.png b/public/elements/tracking_tool_2.png similarity index 100% rename from public/elements/tracing_tool_2.png rename to public/elements/tracking_tool_2.png diff --git a/src/components/add_address_panel/add_address_panel.tsx b/src/components/add_address_panel/add_address_panel.tsx new file mode 100644 index 00000000..955e8d14 --- /dev/null +++ b/src/components/add_address_panel/add_address_panel.tsx @@ -0,0 +1,160 @@ +import {IoIosCloseCircleOutline} from 'react-icons/io'; +import {HiPlus} from 'react-icons/hi'; +import {FaHeart} from 'react-icons/fa'; +import {FiTrash2} from 'react-icons/fi'; +import {useState} from 'react'; +import useAPIResponse from '../../lib/hooks/use_api_response'; +import {HttpMethod} from '../../constants/api_request'; +import {buttonStyle} from '../../constants/config'; + +interface IAddAddressPanelProps { + modalVisible: boolean; + modalClickHandler: () => void; + addAddressHandler: (address: string) => void; +} + +const AddAddressPanel = ({ + modalVisible, + modalClickHandler, + addAddressHandler, +}: IAddAddressPanelProps) => { + // Info: (20240326 - Julian) 是否顯示 Following List + const [visibleFollowingList, setVisibleFollowingList] = useState(false); + // Info: (20240326 - Julian) address 清單 + // ToDo: (20240326 - Julian) 之後應該要改成 + const [preAddAddressList, setPreAddAddressList] = useState(''); + + const {data: followingList, isLoading: isFollowingList} = useAPIResponse( + '/api/v1/app/tracking_tool/add_address_list', + {method: HttpMethod.GET} + ); + + const visibleFollowingListHandler = () => setVisibleFollowingList(prev => !prev); + + // Info: (20240326 - Julian) 點擊 Add 按鈕後的處理 + const addAddressButtonHandler = () => { + addAddressHandler(preAddAddressList); + modalClickHandler(); + }; + // Info: (20240326 - Julian) 如果清單為空,就不能點擊 Add 按鈕 + const addAddressButtonDisabled = preAddAddressList === ''; + + // + const displayPreAddAddressList = + preAddAddressList !== '' ? ( +
+

+ Address + {preAddAddressList} +

+ + {/* Info: (20240326 - Julian) Delete button */} + +
+ ) : null; + + const displayFollowingList = + !isFollowingList && followingList ? ( + followingList.map((address, index) => { + // Info: (20240326 - Julian) 將地址加入清單並關閉 Following List + const addButtonHandler = () => { + setPreAddAddressList(address); + visibleFollowingListHandler(); + }; + return ( +
+

+ Address + {address} +

+ + {/* Info: (20240326 - Julian) Add button */} + +
+ ); + }) + ) : ( +

Loading...

+ ); + + const isShowFollowingList = visibleFollowingList ? ( +
+ {/* Info: (20240326 - Julian) Close button */} + + +

Following List

+ {/* Info: (20240326 - Julian) Address list */} +
+ {displayFollowingList} +
+
+ ) : null; + + const isShowPanel = modalVisible ? ( +
+
+ {/* Info: (20240326 - Julian) Close button */} + + +

Add address

+ {/* Info: (20240326 - Julian) Input address */} +
+ + +
+ {/* Info: (20240326 - Julian) Add button */} + + {/* Info: (20240326 - Julian) Pre add address list */} +
+ {displayPreAddAddressList} +
+ + +
+ {/* Info: (20240326 - Julian) Following list */} + {isShowFollowingList} +
+ ) : null; + + return <>{isShowPanel}; +}; + +export default AddAddressPanel; diff --git a/src/components/pagination/pagination.tsx b/src/components/pagination/pagination.tsx index 69360d0c..84dffdfa 100644 --- a/src/components/pagination/pagination.tsx +++ b/src/components/pagination/pagination.tsx @@ -1,6 +1,6 @@ import {Dispatch, SetStateAction, useEffect} from 'react'; import {RiArrowLeftSLine, RiArrowRightSLine} from 'react-icons/ri'; -import {DEFAULT_PAGE, ITEM_PER_PAGE} from '../../constants/config'; +import {DEFAULT_PAGE, ITEM_PER_PAGE, buttonStyle} from '../../constants/config'; import useStateRef from 'react-usestateref'; import {useRouter} from 'next/router'; import {IAddressHistoryQuery} from '../../constants/api_request'; @@ -36,8 +36,6 @@ const Pagination = ({ // eslint-disable-next-line @typescript-eslint/no-unused-vars const [targetPage, setTargetPage, targetPageRef] = useStateRef(activePage); - const buttonStyle = - 'flex h-48px w-48px items-center justify-center rounded border border-transparent bg-purpleLinear p-3 transition-all duration-300 ease-in-out hover:border-hoverWhite hover:cursor-pointer disabled:opacity-50 disabled:cursor-default disabled:border-transparent'; const router = useRouter(); const {query} = router; diff --git a/src/components/tracking_tool_panel/tracking_tool_panel.tsx b/src/components/tracking_tool_panel/tracking_tool_panel.tsx index 5ede6972..46699352 100644 --- a/src/components/tracking_tool_panel/tracking_tool_panel.tsx +++ b/src/components/tracking_tool_panel/tracking_tool_panel.tsx @@ -1,29 +1,25 @@ import Image from 'next/image'; import {useTranslation} from 'next-i18next'; -import {useState} from 'react'; +import {useState, useContext} from 'react'; +import {TrackingContext, TrackingType} from '../../contexts/tracking_context'; import {TranslateFunction} from '../../interfaces/locale'; import {HiPlus, HiMinus} from 'react-icons/hi'; import {FiDownload, FiUpload} from 'react-icons/fi'; import {FaRegBookmark} from 'react-icons/fa'; import {IoIosArrowUp} from 'react-icons/io'; - -enum TrackingType { - ADDRESS = 'address', - TRANSACTION = 'transaction', -} +import {buttonStyle} from '../../constants/config'; +import {truncateText} from '../../lib/common'; const TrackingToolPanel = () => { const {t}: {t: TranslateFunction} = useTranslation('common'); - // Info: (20240325 - Julian) 顯示切換 - const [targetTrackingType, setTargetTrackingType] = useState(TrackingType.ADDRESS); + const {targetTrackingType, targetTrackingTypeHandler, visibleAddAddressPanelHandler, addAddress} = + useContext(TrackingContext); + // Info: (20240325 - Julian) 縮放比例 const [zoomPercentage, setZoomPercentage] = useState(100); // Info: (20240325 - Julian) 工具列展開 const [isToolbarExpanded, setIsToolbarExpanded] = useState(false); - const handleSwitchToAddress = () => setTargetTrackingType(TrackingType.ADDRESS); - const handleSwitchToTransaction = () => setTargetTrackingType(TrackingType.TRANSACTION); - const handleZoomIn = () => setZoomPercentage(prev => { if (prev >= 200) { @@ -53,15 +49,15 @@ const TrackingToolPanel = () => {

{t('TRACKING_TOOL_PAGE.VIEW')}

{/* Info: (20240325 - Julian) Input button */} - {/* Info: (20240325 - Julian) Output button */} - {/* Info: (20240325 - Julian) Notes button */} -
@@ -73,7 +69,7 @@ const TrackingToolPanel = () => { className={`relative flex w-300px items-center justify-center rounded-full bg-darkPurple px-1 py-1 text-base font-bold ${switchStyle}`} > {/* Info: (20240325 - Julian) Zoom percentage */}

{zoomPercentage} %

{/* Info: (20240325 - Julian) Minus button */} - @@ -144,7 +132,10 @@ const TrackingToolPanel = () => { {/* Info: (20240325 - Julian) Add Address button */} - {toolbarList} + + {/* Info: (20240325 - Julian) Tracking View */} +
{showAddressMap}
); }; diff --git a/src/constants/config.ts b/src/constants/config.ts index dd957bbe..056e3530 100644 --- a/src/constants/config.ts +++ b/src/constants/config.ts @@ -227,6 +227,9 @@ export const THRESHOLD_FOR_BLOCK_STABILITY = { LOW: 10, }; +export const buttonStyle = + 'flex h-48px w-48px items-center justify-center rounded border border-transparent bg-purpleLinear p-3 transition-all duration-300 ease-in-out hover:border-hoverWhite hover:cursor-pointer disabled:opacity-50 disabled:cursor-default disabled:border-transparent'; + export const MAX_64_BIT_INTEGER_PARAMETER = '9223372036854775807'; export const MIN_64_BIT_INTEGER_PARAMETER = '-9223372036854775808'; diff --git a/src/contexts/tracking_context.tsx b/src/contexts/tracking_context.tsx new file mode 100644 index 00000000..9ddbbaf4 --- /dev/null +++ b/src/contexts/tracking_context.tsx @@ -0,0 +1,76 @@ +import {useState, useCallback, createContext} from 'react'; +import AddAddressPanel from '../components/add_address_panel/add_address_panel'; + +export interface ITrackingProvider { + children: React.ReactNode; +} + +export interface ITrackingContext { + targetTrackingType: TrackingType; + targetTrackingTypeHandler: () => void; + + visibleAddAddressPanel: boolean; + visibleAddAddressPanelHandler: () => void; + + addAddress: string; + addAddressHandler: (address: string) => void; +} + +export enum TrackingType { + ADDRESS = 'address', + TRANSACTION = 'transaction', +} + +export const TrackingContext = createContext({ + targetTrackingType: TrackingType.ADDRESS, + targetTrackingTypeHandler: () => null, + + visibleAddAddressPanel: false, + visibleAddAddressPanelHandler: () => null, + + addAddress: '', + addAddressHandler: () => null, +}); + +export const TrackingProvider = ({children}: ITrackingProvider) => { + // Info: (20240326 - Julian) 選擇追蹤的類型 (Address/Transaction) + const [targetTrackingType, setTargetTrackingType] = useState(TrackingType.ADDRESS); + const targetTrackingTypeHandler = useCallback(() => { + setTargetTrackingType(prev => + prev === TrackingType.ADDRESS ? TrackingType.TRANSACTION : TrackingType.ADDRESS + ); + }, []); + + // Info: (20240326 - Julian) 新增地址面板 + const [visibleAddAddressPanel, setVisibleAddAddressPanel] = useState(false); + const visibleAddAddressPanelHandler = useCallback(() => { + setVisibleAddAddressPanel(prev => !prev); + }, []); + + const [addAddress, setAddAddress] = useState(''); + const addAddressHandler = useCallback((address: string) => { + setAddAddress(address); + }, []); + + const defaultValue = { + targetTrackingType, + targetTrackingTypeHandler, + + visibleAddAddressPanel, + visibleAddAddressPanelHandler, + + addAddress, + addAddressHandler, + }; + + return ( + + + {children} + + ); +}; diff --git a/src/pages/api/v1/app/chains/[chains_id]/evidence/[evidence_id]/cash_flow.ts b/src/pages/api/v1/app/chains/[chains_id]/evidence/[evidence_id]/cash_flow.ts index 9a475c85..1197f60d 100644 --- a/src/pages/api/v1/app/chains/[chains_id]/evidence/[evidence_id]/cash_flow.ts +++ b/src/pages/api/v1/app/chains/[chains_id]/evidence/[evidence_id]/cash_flow.ts @@ -2,9 +2,11 @@ import type {NextApiRequest, NextApiResponse} from 'next'; import prisma from '../../../../../../../../../prisma/client'; -import {ICashFlowResponse} from '../../../../../../../../interfaces/cash_flow_neo'; +import { + ICashFlowResponse, + CashFlowNeoSchema, +} from '../../../../../../../../interfaces/cash_flow_neo'; import {IEvidenceContent} from '../../../../../../../../interfaces/evidence'; -import {CashFlowNeoSchema} from '../../../../../../../../interfaces/cash_flow_neo'; type ResponseData = ICashFlowResponse | undefined; diff --git a/src/pages/api/v1/app/tracking_tool/add_address_list.ts b/src/pages/api/v1/app/tracking_tool/add_address_list.ts new file mode 100644 index 00000000..5e47642f --- /dev/null +++ b/src/pages/api/v1/app/tracking_tool/add_address_list.ts @@ -0,0 +1,29 @@ +// 101 - GET /app/tracking-tool add address list + +import type {NextApiRequest, NextApiResponse} from 'next'; +import prisma from '../../../../../../prisma/client'; + +type ResponseData = string[]; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + try { + const addressList = await prisma.addresses.findMany({ + select: { + address: true, + }, + }); + + const result = addressList + .map(address => `${address.address}`) + .filter(address => address !== ''); + + res.status(200).json(result); + } catch (error) { + // Info: (20240326 - Julian) Request error + // eslint-disable-next-line no-console + console.error('app/tracking-tool add address list request', error); + res.status(500).json([]); + } finally { + await prisma.$disconnect(); + } +} diff --git a/src/pages/app/chains/[chainId]/block/[blockId]/index.tsx b/src/pages/app/chains/[chainId]/block/[blockId]/index.tsx index 3f953b56..f7c1deee 100644 --- a/src/pages/app/chains/[chainId]/block/[blockId]/index.tsx +++ b/src/pages/app/chains/[chainId]/block/[blockId]/index.tsx @@ -17,7 +17,7 @@ import {useTranslation} from 'next-i18next'; import {TranslateFunction} from '../../../../../../interfaces/locale'; import {BFAURL, getDynamicUrl} from '../../../../../../constants/url'; import {getChainIcon} from '../../../../../../lib/common'; -import {DEFAULT_CHAIN_ICON} from '../../../../../../constants/config'; +import {DEFAULT_CHAIN_ICON, buttonStyle} from '../../../../../../constants/config'; import DataNotFound from '../../../../../../components/data_not_found/data_not_found'; import {APIURL, HttpMethod} from '../../../../../../constants/api_request'; @@ -55,9 +55,6 @@ const BlockDetailPage = ({blockId, chainId}: IBlockDetailPageProps) => { const previousHandler = () => router.push(previousLink); const nextHandler = () => router.push(nextLink); - const buttonStyle = - 'flex h-48px w-48px items-center justify-center rounded border border-transparent bg-purpleLinear p-3 transition-all duration-300 ease-in-out hover:border-hoverWhite hover:cursor-pointer disabled:opacity-50 disabled:cursor-default disabled:border-transparent'; - // Info: (20231213 - Julian) To check if the previousBlock or nextBlock exist const previousId = blockData?.previousBlockId ? blockData?.previousBlockId : undefined; const nextId = blockData?.nextBlockId ? blockData?.nextBlockId : undefined; diff --git a/src/pages/app/tracking-tool.tsx b/src/pages/app/tracking-tool.tsx index c4da7826..cc958f67 100644 --- a/src/pages/app/tracking-tool.tsx +++ b/src/pages/app/tracking-tool.tsx @@ -7,6 +7,7 @@ import Footer from '../../components/footer/footer'; import Breadcrumb from '../../components/breadcrumb/breadcrumb'; import NavBar from '../../components/nav_bar/nav_bar'; import TrackingToolPanel from '../../components/tracking_tool_panel/tracking_tool_panel'; +import {TrackingProvider} from '../../contexts/tracking_context'; const TrackingToolPage = () => { const {t}: {t: TranslateFunction} = useTranslation('common'); @@ -24,11 +25,12 @@ const TrackingToolPage = () => { ]; return ( - <> + {headTitle} +
@@ -48,7 +50,7 @@ const TrackingToolPage = () => {
- + ); }; diff --git a/tailwind.config.js b/tailwind.config.js index a7ad47d7..10fd05a7 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -373,6 +373,7 @@ module.exports = { zIndex: { 60: 60, 70: 70, + 80: 80, }, gridTemplateRows: { 0: 'repeat(1, minmax(0px, 0fr))',