From 2fbbd9aabc0e032b10c5912dcf0bce0f99a519e2 Mon Sep 17 00:00:00 2001 From: Reed Vogt Date: Sat, 18 Nov 2023 19:31:07 -0800 Subject: [PATCH] updating cards in server correctly --- src/App.js | 15 +- .../{other => chart}/ChartErrorBoundary.jsx | 0 .../{other => chart}/ChartTooltip.jsx | 0 .../{other => chart}/LinearChart.js | 153 +--- src/components/chart/PortfolioChart.jsx | 132 +++ .../{other => chart}/chartUtils.jsx | 39 +- .../cleanUp/CalculateCollectionStatistics.jsx | 57 ++ .../CollectionEditPanel.jsx | 0 src/components/content/PortfolioContent.jsx | 2 +- .../dialogs/ChooseCollectionDialog.jsx | 20 +- .../dialogs/CollectionSelectorDialog.jsx | 20 +- .../dialogs/CreateOrEditCollectionDialog.jsx | 9 +- src/components/dialogs/dialogStyles.jsx | 35 + .../{other => grids}/DeckDisplay.js | 6 +- .../other/CalculateCollectionStatistics.jsx | 57 -- .../other/CollectionStatisticsSelector.jsx | 95 --- .../other/CollectionValueTracker.jsx | 139 ++-- .../{ => InputComponents}/CardNameInput.js | 14 +- .../CollectionStatisticsSelector.jsx | 186 +++++ .../{ => InputComponents}/CustomSelector.js | 0 .../InputComponents/TimeRangeSelector.jsx | 54 ++ .../{ => InputComponents}/UpdateStatusBox.jsx | 4 +- .../InputComponents/UpdateStatusBox2.jsx | 128 +++ src/components/other/PortfolioChart.jsx | 377 --------- src/components/other/TimeRangeSelector.jsx | 57 -- .../{other => reusable}/CustomPagination.jsx | 0 src/components/search/DeckSearch.js | 2 +- src/components/search/SearchBar.js | 4 +- src/containers/PortfolioChartContainer.jsx | 117 +-- .../DeckBuilderContainer.js | 72 +- .../CollectionContext/CollectionContext.jsx | 783 ++++++++++++------ .../checkServerForUpdates.jsx | 66 ++ .../CollectionContext/collectionUtility.jsx | 142 +++- src/context/CombinedProvider.jsx | 716 +++++++++++----- src/index.js | 14 +- src/pages/ProfilePage.js | 18 +- src/themeSettings.jsx | 4 +- 37 files changed, 2041 insertions(+), 1496 deletions(-) rename src/components/{other => chart}/ChartErrorBoundary.jsx (100%) rename src/components/{other => chart}/ChartTooltip.jsx (100%) rename src/components/{other => chart}/LinearChart.js (54%) create mode 100644 src/components/chart/PortfolioChart.jsx rename src/components/{other => chart}/chartUtils.jsx (89%) create mode 100644 src/components/cleanUp/CalculateCollectionStatistics.jsx rename src/components/{other => cleanUp}/CollectionEditPanel.jsx (100%) rename src/components/{other => grids}/DeckDisplay.js (92%) delete mode 100644 src/components/other/CalculateCollectionStatistics.jsx delete mode 100644 src/components/other/CollectionStatisticsSelector.jsx rename src/components/other/{ => InputComponents}/CardNameInput.js (58%) create mode 100644 src/components/other/InputComponents/CollectionStatisticsSelector.jsx rename src/components/other/{ => InputComponents}/CustomSelector.js (100%) create mode 100644 src/components/other/InputComponents/TimeRangeSelector.jsx rename src/components/other/{ => InputComponents}/UpdateStatusBox.jsx (96%) create mode 100644 src/components/other/InputComponents/UpdateStatusBox2.jsx delete mode 100644 src/components/other/PortfolioChart.jsx delete mode 100644 src/components/other/TimeRangeSelector.jsx rename src/components/{other => reusable}/CustomPagination.jsx (100%) create mode 100644 src/context/CollectionContext/checkServerForUpdates.jsx diff --git a/src/App.js b/src/App.js index 92e069a..2f74223 100644 --- a/src/App.js +++ b/src/App.js @@ -113,11 +113,11 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { setLastCronJobTriggerTime(currentTime); if (userId && listOfMonitoredCards) { console.log('RETRIEVING LIST OF MONITORED CARDS (paused)'); - // handleSendAllCardsInCollections( - // userId, - // listOfMonitoredCards - // // handleRetrieveListOfMonitoredCards() - // ); + handleSendAllCardsInCollections( + userId, + listOfMonitoredCards + // handleRetrieveListOfMonitoredCards() + ); console.log('Triggered the cron job.'); } } @@ -140,7 +140,7 @@ const useCronJob = (lastCronJobTriggerTime, setLastCronJobTriggerTime) => { const App = () => { const { user } = useUserContext(); const { isLoading, setIsContextLoading } = useUtilityContext(); - const { fetchAllCollectionsForUser } = useCollectionStore(); + const { fetchAllCollectionsForUser, allCollections } = useCollectionStore(); const [lastCronJobTriggerTime, setLastCronJobTriggerTime] = useState(null); useCronJob(lastCronJobTriggerTime, setLastCronJobTriggerTime); @@ -174,6 +174,7 @@ const App = () => { if (user && isMounted) { try { // const response = fet + await fetchAllCollectionsForUser(user.userID); if (isMounted) { console.log('Fetched collections because none were present.'); // Update state only if the component is still mounted @@ -192,7 +193,7 @@ const App = () => { return () => { isMounted = false; }; - }, [user, fetchAllCollectionsForUser]); + }, [user, fetchAllCollectionsForUser, allCollections]); return ( <> diff --git a/src/components/other/ChartErrorBoundary.jsx b/src/components/chart/ChartErrorBoundary.jsx similarity index 100% rename from src/components/other/ChartErrorBoundary.jsx rename to src/components/chart/ChartErrorBoundary.jsx diff --git a/src/components/other/ChartTooltip.jsx b/src/components/chart/ChartTooltip.jsx similarity index 100% rename from src/components/other/ChartTooltip.jsx rename to src/components/chart/ChartTooltip.jsx diff --git a/src/components/other/LinearChart.js b/src/components/chart/LinearChart.js similarity index 54% rename from src/components/other/LinearChart.js rename to src/components/chart/LinearChart.js index 3a0814a..054239e 100644 --- a/src/components/other/LinearChart.js +++ b/src/components/chart/LinearChart.js @@ -95,26 +95,33 @@ const LinearChart = ({ const theme = useTheme(); const [isZoomed, setIsZoomed] = useState(false); const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - - // const filteredData = useMemo( - // () => getFilteredData(filteredChartData, timeRange), - // [filteredChartData, timeRange] - // ); - // console.log('filteredData', filteredData); - - // const dataForChart = useMemo(() => { - // return datesTimesValues.dates.map((date, index) => ({ - // x: formatDateToString( - // new Date(`${date} ${datesTimesValues.times[index]}`) - // ), - // y: datesTimesValues.values[index], - // })); - // }, [datesTimesValues]); - // CustomLogger('LinearChart', 'info', { - // filteredChartData, - // datesTimesValues, - // }); - const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); + const [format, setFormat] = useState('0,0'); + // Calculate tickValues and xFormat based on timeRange + const { tickValues, xFormat } = useMemo(() => { + let format, ticks; + switch (timeRange) { + case '2 hours': + format = '%H:%M'; + ticks = 'every 15 minutes'; + break; + case '24 hours': + format = '%H:%M'; + ticks = 'every 1 hour'; + break; + case '7 days': + format = '%b %d'; + ticks = 'every 1 day'; + break; + case '1 month': + format = '%b %d'; + ticks = 'every 3 days'; + break; + default: + format = '%b %d'; + ticks = 'every 1 day'; + } + return { tickValues: ticks, xFormat: `time:${format}` }; + }, [timeRange]); if ( !Array.isArray(filteredChartData) || @@ -126,86 +133,6 @@ const LinearChart = ({ ); } - // const classes = useStyles(); - // const theme = useTheme(); - - // // Hooks should be at the top level of your component - // const [isZoomed, setIsZoomed] = useState(false); - // const filteredData = useMemo( - // () => getFilteredData(filteredChartData, timeRange), - // [filteredChartData, timeRange] - // ); - // // const averagedData = useMemo( - // // () => getAveragedData(filteredData), - // // [filteredData] - // // ); - // const { hoveredData, handleMouseMove, handleMouseLeave } = useEventHandlers(); - - // if (!Array.isArray(filteredChartData)) { - // return No valid data available; - // } - - // if ( - // !datesTimesValues || - // !datesTimesValues.dates || - // !datesTimesValues.times || - // !datesTimesValues.values - // ) { - // console.error('Invalid averaged chart data:', datesTimesValues); - // return Invalid data for the chart; - // } - - // const dataForChart = useMemo(() => { - // return [ - // { - // id: 'Averaged Data', - // data: datesTimesValues.dates.map((date, index) => ({ - // x: formatDateToString( - // new Date(`${date} ${datesTimesValues.times[index]}`) - // ), - // y: datesTimesValues.values[index], - // })), - // }, - // ]; - // }, [datesTimesValues]); - - // if (dataForChart[0].data.length === 0) { - // return No valid data available; - // } - // const tickValues = useMemo(() => getTickValues(timeRange), [timeRange]); - - // if ( - // !Array.isArray(filteredChartData) || - // filteredChartData.some((d) => !d.x || !d.y) - // ) { - // return No valid data available; - // } - // // logReadableChartInfo( - // // dataForChart, - // // datesTimesValues, - // // filteredChartData, - // // latestData - // // ); - // try { - // const filteredData = useMemo( - // () => getFilteredData(filteredChartData, timeRange), - // [filteredChartData, timeRange] - // ); - // getAveragedData(filteredData); - // } catch (error) { - // console.error('Error processing data for chart:', error); - // } // console.log('averagedData', averagedData); - - // const lastData = useMemo(() => { - // if (filteredChartData && filteredChartData.length) { - // return { - // x: filteredChartData[filteredChartData.length - 1].x, - // y: filteredChartData[filteredChartData.length - 1].y, - // }; - // } - // return {}; - // }, [filteredChartData]); - const chartProps = { margin: { top: 50, right: 110, bottom: 50, left: 60 }, // data: [{ id: 'Data', data: dataForChart }], @@ -215,20 +142,26 @@ const LinearChart = ({ motionDamping: 15, xScale: { type: 'time', - format: '%Y-%m-%d %H:%M', + // format: '%Y-%m-%d %H:%M:%S', + format: '%Y-%m-%dT%H:%M:%S.%LZ', useUTC: false, - precision: 'minute', + precision: 'second', }, - yScale: { type: 'linear', min: 'auto', max: 'auto' }, + xFormat: 'time:%Y-%m-%d %H:%M:%S', axisBottom: { tickRotation: 0, legendOffset: -12, legend: 'Time', tickPadding: 10, tickSize: 10, - format: '%b %d', + // format: '%b %d', + // tickValues: 'every 2 days', + format: xFormat, tickValues: tickValues, + // tickValues: tickValues, }, + yScale: { type: 'linear', min: 'auto', max: 'auto' }, + axisLeft: { orient: 'left', legend: 'Value ($)', @@ -250,12 +183,12 @@ const LinearChart = ({ onMouseLeave: handleMouseLeave, onClick: () => setIsZoomed(!isZoomed), tooltip: CustomTooltip, - sliceTooltip: ({ slice }) => { - const point = slice.points.find( - (p) => p.id === 'Data' && p.data.x === latestData.x - ); - return point ? : null; - }, + // sliceTooltip: ({ slice }) => { + // const point = slice.points.find( + // (p) => p.id === 'Data' && p.data.x === latestData.x + // ); + // return point ? : null; + // }, }; return ( diff --git a/src/components/chart/PortfolioChart.jsx b/src/components/chart/PortfolioChart.jsx new file mode 100644 index 0000000..d120048 --- /dev/null +++ b/src/components/chart/PortfolioChart.jsx @@ -0,0 +1,132 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Box, Container, Grid, Paper, styled, useTheme } from '@mui/material'; +import LinearChart from './LinearChart'; +import { useChartContext } from '../../context/ChartContext/ChartContext'; +import ErrorBoundary from '../../context/ErrorBoundary'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { useCombinedContext } from '../../context/CombinedProvider'; +import debounce from 'lodash/debounce'; +import { + convertDataForNivo2, + getFilteredData2, + groupAndAverageData, +} from './chartUtils'; + +const ChartPaper = styled(Paper)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius, + boxShadow: theme.shadows[5], + backgroundColor: theme.palette.background.paper, + color: theme.palette.text.secondary, + padding: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '100%', + minHeight: '400px', + overflow: 'hidden', + margin: theme.spacing(2, 0), +})); + +const PortfolioChart = () => { + const theme = useTheme(); + const { latestData, setLatestData, timeRange } = useChartContext(); + const [lastUpdateTime, setLastUpdateTime] = useState(null); + const chartContainerRef = useRef(null); + // const [nivoReadyData2, setNivoReadyData2] = useState(null); // Declare state for nivoReadyData2 + + const [chartDimensions, setChartDimensions] = useState({ + width: 0, + height: 0, + }); + const { selectedCollection } = useCollectionStore(); + + const filteredChartData2 = getFilteredData2(selectedCollection); + // let nivoReadyData2 = []; + // const rawData2 = useMemo( + // () => groupAndAverageData(filteredChartData2, threshold), + // [filteredChartData2, threshold] + // ); + + const handleThresholdUpdate = () => { + const currentTime = new Date().getTime(); + if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { + // 10 minutes + setLastUpdateTime(currentTime); + return currentTime; + } + return lastUpdateTime; + }; + + const threshold = handleThresholdUpdate(); + const rawData2 = useMemo( + () => groupAndAverageData(filteredChartData2, threshold), + [filteredChartData2, threshold] + ); + + const nivoReadyData2 = useMemo( + () => convertDataForNivo2(rawData2), + [rawData2] + ); + + const HEIGHT_TO_WIDTH_RATIO = 2 / 3; + + useEffect(() => { + const handleResize = debounce(() => { + if (chartContainerRef.current) { + const width = chartContainerRef.current.offsetWidth; + const height = width * HEIGHT_TO_WIDTH_RATIO; + setChartDimensions({ width, height }); + } + }, 100); + + window.addEventListener('resize', handleResize); + handleResize(); + + return () => { + window.removeEventListener('resize', handleResize); + handleResize.cancel(); + }; + }, []); + + return ( + + + + + + {filteredChartData2.length > 0 ? ( + + ) : ( +
No data available
+ )} +
+
+
+
+
+ ); +}; + +export default PortfolioChart; diff --git a/src/components/other/chartUtils.jsx b/src/components/chart/chartUtils.jsx similarity index 89% rename from src/components/other/chartUtils.jsx rename to src/components/chart/chartUtils.jsx index 2f5afe4..937da8e 100644 --- a/src/components/other/chartUtils.jsx +++ b/src/components/chart/chartUtils.jsx @@ -7,6 +7,7 @@ import { Box, Typography } from '@mui/material'; import { useTheme } from '@mui/styles'; import { useCallback, useMemo, useState } from 'react'; +import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; export const getUniqueValidData = (currentChartData) => { if (!Array.isArray(currentChartData)) { @@ -48,7 +49,16 @@ export const getUniqueValidData = (currentChartData) => { y: entry.y, })); }; - +export const getFilteredData2 = (selectedCollection) => { + const filteredChartData2 = useMemo(() => { + // const { selectedCollection } = useCollectionStore(); + const allXYValues2 = selectedCollection?.currentChartDataSets2; + // console.log('ALL XY VALUES:', allXYValues2); + return allXYValues2 ? getUniqueValidData(allXYValues2) : []; + }, [selectedCollection]); + + return filteredChartData2; +}; // export const groupAndAverageData = (data, threshold = 600000) => { // // 10 minutes in milliseconds // if (!data || data.length === 0) return { dates: [], times: [], values: [] }; @@ -111,16 +121,16 @@ export const groupAndAverageData = (data, threshold = 600000) => { const clusters = []; let currentCluster = [data[0]]; - console.log('Initial cluster with first data point: ', currentCluster); + // console.log('Initial cluster with first data point: ', currentCluster); for (let i = 1; i < data.length; i++) { const prevTime = new Date(data[i - 1].x).getTime(); const currentTime = new Date(data[i].x).getTime(); const timeDiff = currentTime - prevTime; - console.log( - `Time difference between points ${i - 1} and ${i}: ${timeDiff}ms` - ); + // console.log( + // `Time difference between points ${i - 1} and ${i}: ${timeDiff}ms` + // ); if (timeDiff <= threshold) { currentCluster.push(data[i]); @@ -130,7 +140,7 @@ export const groupAndAverageData = (data, threshold = 600000) => { } } clusters.push(currentCluster); // Push the last cluster - console.log('Final cluster: ', currentCluster); + // console.log('Final cluster: ', currentCluster); // Process each cluster to create the desired output format clusters.map((cluster) => { @@ -149,7 +159,7 @@ export const groupAndAverageData = (data, threshold = 600000) => { }; }); - console.log('Processed clusters: ', clusters); + // console.log('Processed clusters: ', clusters); return clusters; }; @@ -173,10 +183,13 @@ export const getAveragedData = (data) => { export const getTickValues = (timeRange) => { console.log('timeRange: ', timeRange); const mapping = { - '15m': 'every 15 minutes', - '2h': 'every 2 hours', - '1d': 'every day', - '1w': 'every week', + 600000: 'every 10 minutes', + 900000: 'every 15 minutes', + 3600000: 'every hour', + 7200000: 'every 2 hours', + 86400000: 'every day', + 604800000: 'every week', + 2592000000: 'every month', }; return mapping[timeRange] || 'every day'; // Default to 'every week' if no match }; @@ -243,8 +256,8 @@ export const convertDataForNivo2 = (rawData2) => { return []; } - console.log('rawData2: ', rawData2); - console.log('rawData2.data: ', rawData2[0]); + // console.log('rawData2: ', rawData2); + // console.log('rawData2.data: ', rawData2[0]); // rawData is assumed to be an array of objects with 'label', 'x', and 'y' properties const nivoData = rawData2[0].map((dataPoint) => ({ x: dataPoint.x, // x value is already an ISO date string diff --git a/src/components/cleanUp/CalculateCollectionStatistics.jsx b/src/components/cleanUp/CalculateCollectionStatistics.jsx new file mode 100644 index 0000000..fe3693a --- /dev/null +++ b/src/components/cleanUp/CalculateCollectionStatistics.jsx @@ -0,0 +1,57 @@ +// import React, { useMemo } from 'react'; +// import { MenuItem, Select, Typography } from '@mui/material'; + +// // Helper function to calculate statistics +// const calculateStatistics = (data, timeRange) => { +// // Filter the data according to the timeRange +// const filteredData = data.filter( +// (item) => new Date(item.x).getTime() >= Date.now() - timeRange +// ); + +// const prices = filteredData.map((d) => d.y); +// const high = Math.max(...prices); +// const low = Math.min(...prices); +// const percentChange = +// ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100; +// const average = prices.reduce((a, b) => a + b, 0) / prices.length; + +// return { high, low, percentChange, average }; +// }; + +// const StatisticsSelector = ({ data, timeRange }) => { +// const [selectedStat, setSelectedStat] = React.useState(''); + +// // Compute the statistics when the data or timeRange changes +// const stats = useMemo( +// () => calculateStatistics(data, timeRange), +// [data, timeRange] +// ); + +// // Handle selection change +// const handleChange = (event) => { +// setSelectedStat(event.target.value); +// }; + +// return ( +// <> +// +// +// {selectedStat === 'high' && `High: $${stats.high}`} +// {selectedStat === 'low' && `Low: $${stats.low}`} +// {selectedStat === 'percentChange' && +// `Change: ${stats.percentChange.toFixed(2)}%`} +// {selectedStat === 'average' && `24hr Avg: $${stats.average.toFixed(2)}`} +// +// +// ); +// }; + +// export default StatisticsSelector; diff --git a/src/components/other/CollectionEditPanel.jsx b/src/components/cleanUp/CollectionEditPanel.jsx similarity index 100% rename from src/components/other/CollectionEditPanel.jsx rename to src/components/cleanUp/CollectionEditPanel.jsx diff --git a/src/components/content/PortfolioContent.jsx b/src/components/content/PortfolioContent.jsx index 8591778..57d9f1e 100644 --- a/src/components/content/PortfolioContent.jsx +++ b/src/components/content/PortfolioContent.jsx @@ -12,7 +12,7 @@ const PortfolioContent = ({ error, selectedCards, removeCard }) => { flexDirection: 'column', justifyContent: 'center', alignItems: 'center', - p: 2, + // p: 2, }} > diff --git a/src/components/dialogs/ChooseCollectionDialog.jsx b/src/components/dialogs/ChooseCollectionDialog.jsx index 34e5f6d..ea68b2d 100644 --- a/src/components/dialogs/ChooseCollectionDialog.jsx +++ b/src/components/dialogs/ChooseCollectionDialog.jsx @@ -10,26 +10,8 @@ import { Divider, Snackbar, } from '@mui/material'; -import { makeStyles } from '@mui/styles'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; - -const useStyles = makeStyles((theme) => ({ - listItem: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: theme.spacing(2), - backgroundColor: '#ffffff', - borderRadius: '8px', - marginBottom: theme.spacing(2), - boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', - }, - listItemText: { - flex: 1, - textAlign: 'left', - marginLeft: theme.spacing(3), - }, -})); +import { useStyles } from './dialogStyles'; const ChooseCollectionDialog = ({ onSave, isOpen, onClose }) => { const { setSelectedCollection, allCollections } = useCollectionStore(); diff --git a/src/components/dialogs/CollectionSelectorDialog.jsx b/src/components/dialogs/CollectionSelectorDialog.jsx index 045a313..bf7661a 100644 --- a/src/components/dialogs/CollectionSelectorDialog.jsx +++ b/src/components/dialogs/CollectionSelectorDialog.jsx @@ -9,25 +9,7 @@ import { ListItemText, Divider, } from '@mui/material'; -import { makeStyles } from '@mui/styles'; - -const useStyles = makeStyles((theme) => ({ - listItem: { - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - padding: theme.spacing(2), - backgroundColor: '#ffffff', - borderRadius: '8px', - marginBottom: theme.spacing(2), - boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', - }, - listItemText: { - flex: 1, - textAlign: 'left', - marginLeft: theme.spacing(3), - }, -})); +import { useStyles } from './dialogStyles'; const CollectionSelectorDialog = ({ open, diff --git a/src/components/dialogs/CreateOrEditCollectionDialog.jsx b/src/components/dialogs/CreateOrEditCollectionDialog.jsx index 7da668b..1740e0b 100644 --- a/src/components/dialogs/CreateOrEditCollectionDialog.jsx +++ b/src/components/dialogs/CreateOrEditCollectionDialog.jsx @@ -26,6 +26,7 @@ const CreateOrEditCollectionDialog = ({ addOneToCollection, removeCollection, selectedCollection, + updateCollectionDetails, } = useCollectionStore(); const [cookies] = useCookies(['user']); const userId = cookies.user?.id; @@ -35,6 +36,7 @@ const CreateOrEditCollectionDialog = ({ name: editedName, description: editedDescription, userId: userId, + tag: 'new', }; if (isNew) { @@ -46,7 +48,12 @@ const CreateOrEditCollectionDialog = ({ userId ); } else if (editedName && editedDescription) { - addOneToCollection(newCollectionInfo); + // addOneToCollection(newCollectionInfo); + updateCollectionDetails( + newCollectionInfo, + userId, + selectedCollection._id + ); } else { console.error('No card to add to the collection'); } diff --git a/src/components/dialogs/dialogStyles.jsx b/src/components/dialogs/dialogStyles.jsx index e69de29..a7f112c 100644 --- a/src/components/dialogs/dialogStyles.jsx +++ b/src/components/dialogs/dialogStyles.jsx @@ -0,0 +1,35 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +import { makeStyles } from '@material-ui/core/styles'; + +export const useStyles = makeStyles((theme) => ({ + root: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'stretch', + height: '100%', + width: '50vw', + padding: theme.spacing(2), + }, + button: { + marginBottom: theme.spacing(2), + }, + list: { + flexGrow: 1, + }, + listItem: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2), + backgroundColor: '#ffffff', + borderRadius: '8px', + marginBottom: theme.spacing(2), + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)', + }, + listItemText: { + flex: 1, + textAlign: 'left', + marginLeft: theme.spacing(3), + }, +})); diff --git a/src/components/other/DeckDisplay.js b/src/components/grids/DeckDisplay.js similarity index 92% rename from src/components/other/DeckDisplay.js rename to src/components/grids/DeckDisplay.js index 4309e4e..fcce34c 100644 --- a/src/components/other/DeckDisplay.js +++ b/src/components/grids/DeckDisplay.js @@ -1,9 +1,9 @@ import React, { useContext, useEffect, useState } from 'react'; import { Paper, Button } from '@mui/material'; import { DeckContext } from '../../context/DeckContext/DeckContext'; -import DeckButtonList from '../grids/deckBuilderGrids/DeckButtonList'; -import CardsGrid from '../grids/deckBuilderGrids/CardsGrid'; -import DeckEditPanel from './DeckEditPanel'; +import DeckButtonList from './deckBuilderGrids/DeckButtonList'; +import CardsGrid from './deckBuilderGrids/CardsGrid'; +import DeckEditPanel from '../other/DeckEditPanel'; import { makeStyles } from '@mui/styles'; const useStyles = makeStyles((theme) => ({ diff --git a/src/components/other/CalculateCollectionStatistics.jsx b/src/components/other/CalculateCollectionStatistics.jsx deleted file mode 100644 index 83f4eaf..0000000 --- a/src/components/other/CalculateCollectionStatistics.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useMemo } from 'react'; -import { MenuItem, Select, Typography } from '@mui/material'; - -// Helper function to calculate statistics -const calculateStatistics = (data, timeRange) => { - // Filter the data according to the timeRange - const filteredData = data.filter( - (item) => new Date(item.x).getTime() >= Date.now() - timeRange - ); - - const prices = filteredData.map((d) => d.y); - const high = Math.max(...prices); - const low = Math.min(...prices); - const percentChange = - ((prices[prices.length - 1] - prices[0]) / prices[0]) * 100; - const average = prices.reduce((a, b) => a + b, 0) / prices.length; - - return { high, low, percentChange, average }; -}; - -const StatisticsSelector = ({ data, timeRange }) => { - const [selectedStat, setSelectedStat] = React.useState(''); - - // Compute the statistics when the data or timeRange changes - const stats = useMemo( - () => calculateStatistics(data, timeRange), - [data, timeRange] - ); - - // Handle selection change - const handleChange = (event) => { - setSelectedStat(event.target.value); - }; - - return ( - <> - - - {selectedStat === 'high' && `High: $${stats.high}`} - {selectedStat === 'low' && `Low: $${stats.low}`} - {selectedStat === 'percentChange' && - `Change: ${stats.percentChange.toFixed(2)}%`} - {selectedStat === 'average' && `24hr Avg: $${stats.average.toFixed(2)}`} - - - ); -}; - -export default StatisticsSelector; diff --git a/src/components/other/CollectionStatisticsSelector.jsx b/src/components/other/CollectionStatisticsSelector.jsx deleted file mode 100644 index ce90125..0000000 --- a/src/components/other/CollectionStatisticsSelector.jsx +++ /dev/null @@ -1,95 +0,0 @@ -import React, { useState, useMemo } from 'react'; -import { MenuItem, Select, Typography, Box } from '@mui/material'; - -function calculateStatistics(data, timeRange) { - if (!data || data.length === 0) { - return {}; // Return an empty object if data is not available - } - - const filteredData = data.filter( - (item) => new Date(item.x).getTime() >= Date.now() - timeRange - ); - - if (filteredData.length === 0) { - return {}; // Return an empty object if filtered data is not available - } - - const sortedData = [...filteredData].sort((a, b) => a.y - b.y); - const highPoint = sortedData.at(-1)?.y || 0; - const lowPoint = sortedData[0]?.y || 0; - const percentChange = - sortedData.length > 1 ? ((highPoint - lowPoint) / lowPoint) * 100 : 0; - const sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); - const average = sum / filteredData.length || 0; - const volume = filteredData.length; - - const mean = sum / volume; - const squaredDiffs = filteredData.map((item) => { - const diff = item.y - mean; - return diff * diff; - }); - const volatility = Math.sqrt( - squaredDiffs.reduce((a, b) => a + b, 0) / volume - ); - - return { - highPoint: highPoint.toFixed(2), - lowPoint: lowPoint.toFixed(2), - percentChange: percentChange.toFixed(2), - average: average.toFixed(2), - volume, - volatility: volatility.toFixed(2), - }; -} - -const CollectionStatisticsSelector = ({ data, timeRange }) => { - const [selectedStat, setSelectedStat] = useState(''); - - const stats = useMemo( - () => calculateStatistics(data, timeRange), - [data, timeRange] - ); - - const handleChange = (event) => { - setSelectedStat(event.target.value); - }; - - return ( - - - - - {selectedStat && - `${selectedStat.replace(/([A-Z])/g, ' $1').trim()}: $${ - stats[selectedStat] - }`} - - - ); -}; - -export default CollectionStatisticsSelector; diff --git a/src/components/other/CollectionValueTracker.jsx b/src/components/other/CollectionValueTracker.jsx index 3c828cf..df3f7f8 100644 --- a/src/components/other/CollectionValueTracker.jsx +++ b/src/components/other/CollectionValueTracker.jsx @@ -1,70 +1,89 @@ -import React, { useState, useEffect } from 'react'; -import { Typography, Box, useTheme } from '@mui/material'; -import { useCombinedContext } from '../../context/CombinedProvider'; +import React from 'react'; +import { Typography, Box, useTheme, Grid } from '@mui/material'; +import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; +import { styled } from '@mui/styles'; -const CollectionValueTracker = ({ data }) => { - const theme = useTheme(); - const { allCardPrices } = useCombinedContext(); - const { totalCost } = useCollectionStore(); - const [totalValue, setTotalValue] = useState(0); - const [changeOverTime, setChangeOverTime] = useState(0); - console.log('allCardPrices', data.allCardPrices); - // useEffect(() => { - // const allPrices = data?.cards?.map((card) => card?.price); - // console.log('[1]allPrices', allPrices); - // const newValue = data?cards?.reduce((acc, card) => acc + (card?.price, 0)); - // console.log('[2]newValue', newValue); - // const change = data?cards?.reduce( - // (acc, card) => acc + (card?.latestPrice?.num - card?.lastSavedPrice?.num), - // 0 - // ); - // console.log('[3]change', change); +const StatBox = styled(Box)(({ theme }) => ({ + backgroundColor: theme.palette.background.paper, + borderRadius: theme.shape.borderRadius, + padding: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-around', + boxShadow: theme.shadows[2], +})); + +const StatItem = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + marginBottom: theme.spacing(1), +})); - // setTotalValue(newValue); - // setChangeOverTime(change); - // }, [data]); +const CollectionValueTracker = ({ stats }) => { + const theme = useTheme(); + const { + totalCost, + selectedCollection, + getTotalPrice, + getTotalPrice2, + totalPrice, + allCardPrices, + } = useCollectionStore(); + const twentyFourHourChange = stats.twentyFourHourAverage; + const newTotal = getTotalPrice(); - // useEffect(() => { - // // Update total value based on allCardPrices - // if (Array.isArray(allCardPrices) && allCardPrices?.length > 0) { - // const total = allCardPrices - // .map((price) => parseFloat(price)) // Convert each price to a number - // .filter((price) => !isNaN(price)) // Filter out non-numeric values - // .reduce((acc, price) => acc + price, 0); // Sum up all prices - // console.log('total', total); - // setTotalValue(total); - // } - // }, [allCardPrices]); + console.log('newTotal:', newTotal); - const trend = changeOverTime > 0 ? 'increased' : 'decreased'; - const trendColor = changeOverTime > 0 ? 'success.main' : 'error.main'; + const statsArray = [ + { + label: 'Total Collection Value', + value: `$${newTotal}`, + }, + { + label: '24 Hour Change', + value: `${twentyFourHourChange?.percentageChange}`, + isIncrease: twentyFourHourChange?.priceIncreased, + }, + // Additional stats + { label: 'Last Price', value: `${twentyFourHourChange?.lastPrice}` }, + { label: 'Average Price', value: `${twentyFourHourChange?.averagePrice}` }, + { label: 'Volume', value: `${twentyFourHourChange?.volume}` }, + { label: 'Volatility', value: `${twentyFourHourChange?.volatility}` }, + { label: 'High Point', value: `${twentyFourHourChange?.highPoint}` }, + { label: 'Low Point', value: `${twentyFourHourChange?.lowPoint}` }, + ]; - console.log('totalValue', totalValue); return ( - - - Total Collection Value: ${totalCost?.toFixed(2)} - - - Value {trend} in the last 24h: ${Math.abs(changeOverTime)?.toFixed(2)} - - + + {statsArray.map((stat, index) => { + const IconComponent = + stat?.isIncrease !== undefined + ? stat?.isIncrease + ? ArrowUpwardIcon + : ArrowDownwardIcon + : null; + const iconColor = stat?.isIncrease + ? theme.palette.success.main + : theme.palette.error.main; + + return ( + + {IconComponent && ( + + )} + + {stat?.label}: {stat?.value} + + + ); + })} + ); }; diff --git a/src/components/other/CardNameInput.js b/src/components/other/InputComponents/CardNameInput.js similarity index 58% rename from src/components/other/CardNameInput.js rename to src/components/other/InputComponents/CardNameInput.js index d9b600b..3be6fd7 100644 --- a/src/components/other/CardNameInput.js +++ b/src/components/other/InputComponents/CardNameInput.js @@ -1,22 +1,10 @@ import React from 'react'; import { Input } from '@mui/material'; -import { useCardStore } from '../../context/CardContext/CardStore'; +import { useCardStore } from '../../../context/CardContext/CardStore'; const CardNameInput = ({ value, setValue }) => { const { handleRequest } = useCardStore(); - // const handleKeyDown = (event) => { - // if (event.key === 'Enter') { - // handleRequest(searchParams); // Use handleRequest from context - // } - // }; - - // const handleChange = (event) => { - // setSearchParams((prevState) => ({ - // ...prevState, - // name: event.target.value, - // })); - // }; const handleKeyDown = (event) => { if (event.key === 'Enter') { handleRequest(); // Use handleRequest from context diff --git a/src/components/other/InputComponents/CollectionStatisticsSelector.jsx b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx new file mode 100644 index 0000000..4b7b125 --- /dev/null +++ b/src/components/other/InputComponents/CollectionStatisticsSelector.jsx @@ -0,0 +1,186 @@ +import React, { useState, useMemo } from 'react'; +import { + MenuItem, + Select, + Typography, + Box, + Grid, + CardContent, + Card, +} from '@mui/material'; +import { getFilteredData2 } from '../../chart/chartUtils'; +import { useCollectionStore } from '../../../context/CollectionContext/CollectionContext'; + +function calculatePriceChanges(data) { + const sortedData = data.sort((a, b) => new Date(a.x) - new Date(b.x)); + const latestDataPoint = sortedData[sortedData.length - 1]; + const latestTime = new Date(latestDataPoint.x).getTime(); + const twentyFourHoursAgo = latestTime - 24 * 60 * 60 * 1000; + + // Find the data point closest to 24 hours before the latest data point + let closestIndex = -1; + let closestTimeDifference = Number.MAX_SAFE_INTEGER; + + for (let i = 0; i < sortedData.length - 1; i++) { + const time = new Date(sortedData[i].x).getTime(); + const timeDifference = Math.abs(time - twentyFourHoursAgo); + + if (timeDifference < closestTimeDifference) { + closestTimeDifference = timeDifference; + closestIndex = i; + } + } + + if (closestIndex !== -1) { + const pastPrice = sortedData[closestIndex].y; + // console.log('pastPrice', pastPrice); + const priceChange = latestDataPoint.y - pastPrice; + // console.log('priceChange', priceChange); + const percentageChange = ((priceChange / pastPrice) * 100).toFixed(2); + // console.log('percentageChange', percentageChange); + + return [ + { + startDate: sortedData[closestIndex].x, + lowPoint: pastPrice.toFixed(2), + highPoint: latestDataPoint?.y?.toFixed(2), + endDate: latestDataPoint?.x, + priceChange: priceChange.toFixed(2), + percentageChange: `${percentageChange}%`, + priceIncreased: priceChange > 0, + }, + ]; + } + + return []; +} + +export const calculateStatistics = (data, timeRange) => { + if (!data || data.length === 0) return {}; + const filteredData = data?.data?.filter( + (item) => new Date(item?.x).getTime() >= Date.now() - timeRange + ); + if (filteredData.length === 0) return {}; + let highPoint = 0; + let lowPoint = 0; + let sum = 0; + let averageData = 0; + let average = 0; + let volume = 0; + let mean = 0; + let squaredDiffs = 0; + let volatility = 0; + // const filteredData2 = getFilteredData2(data, timeRange); + // console.log('filteredData2', filteredData2); + // console.log('filteredData', filteredData); + for (const data of filteredData) { + highPoint = Math.max(...filteredData.map((item) => item.y)); + lowPoint = Math.min(...filteredData.map((item) => item.y)); + sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); + averageData = calculatePriceChanges(filteredData); + average = sum / filteredData.length || 0; + volume = filteredData.length; + mean = sum / volume; + squaredDiffs = filteredData.map((item) => { + const diff = item.y - mean; + return diff * diff; + }); + volatility = Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / volume); + } + // const highPoint = Math.max(...filteredData.map((item) => item.y)); + // const lowPoint = Math.min(...filteredData.map((item) => item.y)); + // const sum = filteredData.reduce((acc, curr) => acc + curr.y, 0); + // const averageData = calculatePriceChanges(filteredData); + // console.log('averageData', averageData); + // const average = sum / filteredData.length || 0; + // const volume = filteredData.length; + // const mean = sum / volume; + + // const squaredDiffs = filteredData.map((item) => { + // const diff = item.y - mean; + // return diff * diff; + // }); + + // const volatility = Math.sqrt( + // squaredDiffs.reduce((a, b) => a + b, 0) / volume + // ); + + return { + highPoint: highPoint.toFixed(2), + lowPoint: lowPoint.toFixed(2), + twentyFourHourAverage: { + startDate: averageData[0]?.startDate, + endDate: averageData[0]?.endDate, + lowPoint: averageData[0]?.lowPoint, + highPoint: averageData[0]?.highPoint, + priceChange: averageData[0]?.priceChange, + percentageChange: averageData[0]?.percentageChange, + priceIncreased: averageData[0]?.priceIncreased, + }, + average: average?.toFixed(2), + volume, + volatility: volatility?.toFixed(2), + }; +}; + +const CollectionStatisticsSelector = ({ data, timeRange, stats }) => { + const [selectedStat, setSelectedStat] = useState(''); + + const handleChange = (event) => setSelectedStat(event.target.value); + + const StatCard = ({ title, value }) => ( + + + + {title} + + + {value} + + + + ); + + return ( + + + + + {selectedStat && ( + + + + )} + + + ); +}; + +export default CollectionStatisticsSelector; diff --git a/src/components/other/CustomSelector.js b/src/components/other/InputComponents/CustomSelector.js similarity index 100% rename from src/components/other/CustomSelector.js rename to src/components/other/InputComponents/CustomSelector.js diff --git a/src/components/other/InputComponents/TimeRangeSelector.jsx b/src/components/other/InputComponents/TimeRangeSelector.jsx new file mode 100644 index 0000000..21a489f --- /dev/null +++ b/src/components/other/InputComponents/TimeRangeSelector.jsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { MenuItem, Select } from '@mui/material'; +import { useChartContext } from '../../../context/ChartContext/ChartContext'; + +// Remove setTimeRange(value); from here +// const TimeRangeSelector = ({ onChange }) => { +// const { timeRanges, timeRange, setTimeRange, handleChange, currentValue } = +// useChartContext(); +// console.log('timeRanges: ', timeRanges); +// console.log('timeRange: ', timeRange); +// console.log('currentValue: ', currentValue); +// const isInRange = timeRanges.some((option) => option.value === currentValue); +// console.log('isInRange: ', isInRange); +// const safeTimeRange = isInRange ? timeRange : timeRanges[1].value; +// console.log('safeTimeRange: ', safeTimeRange); + +// return ( +// +// ); +// }; + +// export default TimeRangeSelector; + +const TimeRangeSelector = () => { + const { timeRange, timeRanges, handleChange } = useChartContext(); + + return ( + + ); +}; + +export default TimeRangeSelector; diff --git a/src/components/other/UpdateStatusBox.jsx b/src/components/other/InputComponents/UpdateStatusBox.jsx similarity index 96% rename from src/components/other/UpdateStatusBox.jsx rename to src/components/other/InputComponents/UpdateStatusBox.jsx index 7398fff..bfbfff1 100644 --- a/src/components/other/UpdateStatusBox.jsx +++ b/src/components/other/InputComponents/UpdateStatusBox.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Typography } from '@mui/material'; -import { useCombinedContext } from '../../context/CombinedProvider'; +import { useCombinedContext } from '../../../context/CombinedProvider'; import { useCookies } from 'react-cookie'; const UpdateStatusBox = ({ socket }) => { @@ -36,7 +36,7 @@ const UpdateStatusBox = ({ socket }) => { if (socket) { socket.emit('STATUS_UPDATE_REQUEST', { message: 'Requesting status update...', - data: listOfSimulatedCards, + data: listOfMonitoredCards, }); } }; diff --git a/src/components/other/InputComponents/UpdateStatusBox2.jsx b/src/components/other/InputComponents/UpdateStatusBox2.jsx new file mode 100644 index 0000000..53d7a35 --- /dev/null +++ b/src/components/other/InputComponents/UpdateStatusBox2.jsx @@ -0,0 +1,128 @@ +import React, { useState, useEffect } from 'react'; +import { Snackbar, Typography } from '@mui/material'; +import { useCombinedContext } from '../../../context/CombinedProvider'; +import { useCookies } from 'react-cookie'; + +const styles = { + container: { + padding: '15px', + border: '2px solid #444', + borderRadius: '8px', + backgroundColor: '#222', + color: '#fff', + // margin: '20px auto', + boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)', + fontFamily: '"Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + maxWidth: '400px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + alignItems: 'center', + height: '100%', // Adjust height here + width: '100%', // Adjust width here + }, + statusBox: { + marginTop: '15px', + padding: '10px', + background: '#333', + borderRadius: '6px', + border: '1px solid #555', + }, + button: { + padding: '10px 20px', + marginTop: '10px', + border: 'none', + borderRadius: '5px', + cursor: 'pointer', + backgroundColor: '#5CDB95', + color: 'white', + fontWeight: 'bold', + fontSize: '14px', + letterSpacing: '1px', + outline: 'none', + }, +}; + +const UpdateStatusBox2 = ({ socket }) => { + const { + allCollectionData, + listOfMonitoredCards, + handleSendAllCardsInCollections, + } = useCombinedContext(); + const [currentTime, setCurrentTime] = useState(new Date()); + const [updateStatus, setUpdateStatus] = useState('Waiting for cron...'); + // const [openSnackbar, setOpenSnackbar] = useState(false); + const [cookies] = useCookies(['user']); + const [snackbarData, setSnackbarData] = useState({ + open: false, + message: '', + }); + const userId = cookies.user?.id; + const openSnackbar = (message) => { + setSnackbarData({ open: true, message }); + }; + useEffect(() => { + const timeInterval = setInterval(() => { + setCurrentTime(new Date()); + }, 1000); + + const handleStatusUpdate = (statusUpdate) => { + setUpdateStatus(statusUpdate.message || 'Waiting for updates...'); + }; + + if (socket) { + socket.on('INITIAL_RESPONSE', handleStatusUpdate); + } + + // Cleanup function + return () => { + clearInterval(timeInterval); + if (socket) { + socket.off('INITIAL_RESPONSE', handleStatusUpdate); + } + }; + }, [socket]); + const handleTriggerCronJob = () => { + console.log('TRIGGERING CRON JOB'); + console.log('USER ID:', userId); + console.log('LIST OF MONITORED CARDS:', listOfMonitoredCards); + if (userId && listOfMonitoredCards) { + handleSendAllCardsInCollections(userId, listOfMonitoredCards); + console.log('SENDING ALL CARDS IN COLLECTIONS'); + openSnackbar('Triggered the cron job.'); + } + }; + + // const sendUpdateRequest = () => { + // if (socket) { + // socket.emit('STATUS_UPDATE_REQUEST', { + // message: 'Requesting status update...', + // data: listOfMonitoredCards, + // }); + // } + // }; + + // Styling for dark theme + + return ( +
+ + Current Time: {currentTime.toLocaleTimeString()} + +
+ Update Status: {updateStatus} +
+ + setSnackbarData({ ...snackbarData, open: false })} + message={snackbarData.message} + /> +
+ ); +}; + +export default UpdateStatusBox2; diff --git a/src/components/other/PortfolioChart.jsx b/src/components/other/PortfolioChart.jsx deleted file mode 100644 index 6d38aa8..0000000 --- a/src/components/other/PortfolioChart.jsx +++ /dev/null @@ -1,377 +0,0 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { - Box, - Container, - Grid, - MenuItem, - Paper, - Select, - styled, - useTheme, - // debounce, -} from '@mui/material'; -import LinearChart from './LinearChart'; -import { useChartContext } from '../../context/ChartContext/ChartContext'; -import ErrorBoundary from '../../context/ErrorBoundary'; -import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -import { useCombinedContext } from '../../context/CombinedProvider'; -import debounce from 'lodash/debounce'; -import { - convertDataForNivo, - getUniqueValidData, - groupAndAverageData, - convertDataForNivo2, -} from './chartUtils'; - -// const ChartPaper = styled(Paper)(({ theme }) => ({ -// borderRadius: theme.shape.borderRadius, -// boxShadow: theme.shadows[5], -// backgroundColor: theme.palette.background.default, -// color: theme.palette.text.secondary, -// padding: theme.spacing(3), -// [theme.breakpoints.down('sm')]: { minWidth: '300px' }, -// [theme.breakpoints.up('md')]: { minWidth: '500px' }, -// [theme.breakpoints.up('lg')]: { minWidth: '700px' }, -// })); -const ChartPaper = styled(Paper)(({ theme }) => ({ - borderRadius: theme.shape.borderRadius, - boxShadow: theme.shadows[5], - backgroundColor: theme.palette.background.default, - color: theme.palette.text.primary, - padding: theme.spacing(3), - minHeight: '400px', - width: '100%', - display: 'flex', - marginLeft: 'auto', - marginRight: 'auto', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - overflow: 'hidden', -})); -const PortfolioChart = () => { - const theme = useTheme(); - const { latestData, setLatestData, timeRange, timeRanges, currentValue } = - useChartContext(); - const [lastUpdateTime, setLastUpdateTime] = useState(null); - const chartContainerRef = useRef(null); - const [chartDimensions, setChartDimensions] = useState({ - width: 0, - height: 0, - }); - const { selectedCollection } = useCollectionStore(); - let nivoReadyData = null; - let nivoReadyData2 = null; - const { socket } = useCombinedContext(); - const updateLastTimeAndGetThreshold = () => { - const currentTime = new Date().getTime(); - if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { - // 10 minutes - setLastUpdateTime(currentTime); - // setLatestData(filteredChartData[filteredChartData.length - 1]); - setLatestData(filteredChartData2[filteredChartData2.length - 1]); - } - return currentTime; - }; - // const filteredChartData = useMemo(() => { - // const allXYValues = selectedCollection?.chartData?.allXYValues; - // return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; - // }, [selectedCollection]); - // Filtered chart data based on unique valid data - // const filteredChartData = useMemo(() => { - // const currentChartData = selectedCollection?.currentChartDataSets; - // return currentChartData ? getUniqueValidData(currentChartData) : []; - // }, [selectedCollection]); - const filteredChartData2 = useMemo(() => { - const allXYValues2 = selectedCollection?.currentChartDataSets2; - console.log('ALL XY VALUES:', allXYValues2); - return allXYValues2 ? getUniqueValidData(allXYValues2) : []; - }, [selectedCollection]); - - // Calculate threshold based on the last update time - const threshold = updateLastTimeAndGetThreshold(); - - // Group and average data using the calculated threshold - // const rawData = useMemo( - // () => groupAndAverageData(filteredChartData, threshold), - // [filteredChartData, threshold] - // ); - const rawData2 = useMemo( - () => groupAndAverageData(filteredChartData2, threshold), - [filteredChartData2, threshold] - ); - // console.log('FILTERED CHART DATA:', filteredChartData); - // console.log('FILTERED CHART DATA 2:', filteredChartData2); - // const threshold = useMemo(() => timeRange * 0.1, [timeRange]); - - console.log('THRESHOLD:', threshold); - // if (!rawData) { - // console.log('NO RAW DATA!'); - // } - // if (rawData) { - // console.log('RAW DATA:', rawData); - // nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); - // } - if (rawData2) { - console.log('RAW DATA 2:', rawData2); - const nivoReady = useMemo(() => convertDataForNivo2(rawData2), [rawData2]); - nivoReadyData2 = nivoReady; - console.log('NIVO READY DATA 2:', nivoReadyData2); - } - - // Now use this threshold when calling your data grouping function - - useEffect(() => { - const handleResize = debounce(() => { - if (chartContainerRef.current) { - setChartDimensions({ - width: chartContainerRef.current.offsetWidth, - height: chartContainerRef.current.offsetHeight, - }); - } - }, 100); - - window.addEventListener('resize', handleResize); - - handleResize(); - - return () => { - window.removeEventListener('resize', handleResize); - handleResize.cancel(); - }; - }, []); - return ( - - - - {/* - - - - */} - - - {filteredChartData2.length > 0 ? ( - - ) : ( -
No data available
- )} -
-
-
-
-
- ); -}; - -export default PortfolioChart; - -// import React, { useEffect, useMemo, useRef, useState } from 'react'; -// import { -// Box, -// Container, -// Grid, -// MenuItem, -// Paper, -// Select, -// styled, -// useTheme, -// // debounce, -// } from '@mui/material'; -// import LinearChart from './LinearChart'; -// import TimeRangeSelector from './TimeRangeSelector'; -// import { useChartContext } from '../../context/ChartContext/ChartContext'; -// import ErrorBoundary from '../../context/ErrorBoundary'; -// import { useCollectionStore } from '../../context/CollectionContext/CollectionContext'; -// import CollectionStatisticsSelector from './CollectionStatisticsSelector'; -// import UpdateStatusBox from './UpdateStatusBox'; -// import { useCombinedContext } from '../../context/CombinedProvider'; -// import debounce from 'lodash/debounce'; -// import { -// convertDataForNivo, -// getUniqueFilteredXYValues, -// groupAndAverageData, -// } from './chartUtils'; - -// const ChartPaper = styled(Paper)(({ theme }) => ({ -// borderRadius: theme.shape.borderRadius, -// boxShadow: theme.shadows[5], -// backgroundColor: theme.palette.background.default, -// color: theme.palette.text.secondary, -// padding: theme.spacing(3), -// [theme.breakpoints.down('sm')]: { minWidth: '300px' }, -// [theme.breakpoints.up('md')]: { minWidth: '500px' }, -// [theme.breakpoints.up('lg')]: { minWidth: '700px' }, -// })); - -// const PortfolioChart = () => { -// const theme = useTheme(); -// const { selectedCollection } = useCollectionStore(); -// const { timeRange } = useChartContext(); -// const chartContainerRef = useRef(null); -// const [chartDimensions, setChartDimensions] = useState({ -// width: 400, -// height: 400, -// }); -// // const { latestData, setLatestData, timeRange, timeRanges } = -// useChartContext(); -// const [lastUpdateTime, setLastUpdateTime] = useState(null); -// const { socket } = useCombinedContext(); - -// const filteredChartData = useMemo(() => { -// const allXYValues = selectedCollection?.chartData?.allXYValues; -// return allXYValues ? getUniqueFilteredXYValues(allXYValues) : []; -// }, [selectedCollection]); - -// // const threshold = useMemo(() => timeRange * 0.1, [timeRange]); -// // const rawData = useMemo( -// // () => groupAndAverageData(filteredChartData, threshold), -// // [filteredChartData, threshold] -// // ); -// // const nivoReadyData = useMemo(() => convertDataForNivo(rawData), [rawData]); - -// // Now use this threshold when calling your data grouping function - -// // Now use this threshold when calling your data grouping function -// const updateLastTime = () => { -// const currentTime = new Date().getTime(); -// if (!lastUpdateTime || currentTime - lastUpdateTime >= 600000) { -// setLastUpdateTime(currentTime); -// const lastDataset = filteredChartData[filteredChartData.length - 1]; -// lastDataset && setLatestData(lastDataset); -// } -// }; -// const filteredChartData = useMemo(() => { -// // Process data for the chart here using selectedCollection -// return selectedCollection?.chartData?.allXYValues || []; -// }, [selectedCollection]); - -// useEffect(() => { -// const handleResize = debounce(() => { -// if (chartContainerRef.current) { -// setChartDimensions({ -// width: chartContainerRef.current.offsetWidth, -// height: chartContainerRef.current.offsetHeight, -// }); -// } -// }, 100); - -// const resizeObserver = new ResizeObserver(handleResize); -// if (chartContainerRef.current) { -// resizeObserver.observe(chartContainerRef.current); -// } - -// return () => { -// resizeObserver.disconnect(); -// handleResize.cancel(); -// }; -// }, []); -// return ( -// -// -// {/* -// -// -// -// */} -// -// -// {filteredChartData.length > 0 ? ( -// -// ) : ( -//
No data available
-// )} -//
-//
-//
-//
-// ); -// }; - -// export default PortfolioChart; diff --git a/src/components/other/TimeRangeSelector.jsx b/src/components/other/TimeRangeSelector.jsx deleted file mode 100644 index 7ab072d..0000000 --- a/src/components/other/TimeRangeSelector.jsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react'; -import { MenuItem, Select } from '@mui/material'; -import { useChartContext } from '../../context/ChartContext/ChartContext'; - -// Remove setTimeRange(value); from here -const TimeRangeSelector = ({ onChange }) => { - const { timeRanges, timeRange, setTimeRange, handleChange, currentValue } = - useChartContext(); - console.log('timeRanges: ', timeRanges); - console.log('timeRange: ', timeRange); - console.log('currentValue: ', currentValue); - const isInRange = timeRanges.some((option) => option.value === currentValue); - console.log('isInRange: ', isInRange); - const safeTimeRange = isInRange ? timeRange : timeRanges[1].value; - console.log('safeTimeRange: ', safeTimeRange); - - return ( - - ); -}; - -export default TimeRangeSelector; -import React from 'react'; -import { Select, MenuItem } from '@mui/material'; -import { useChartContext } from './ChartContext'; // adjust the import path as necessary - -const TimeRangeSelector = () => { - const { timeRange, timeRanges, handleChange } = useChartContext(); - - return ( - - ); -}; - -export default TimeRangeSelector; diff --git a/src/components/other/CustomPagination.jsx b/src/components/reusable/CustomPagination.jsx similarity index 100% rename from src/components/other/CustomPagination.jsx rename to src/components/reusable/CustomPagination.jsx diff --git a/src/components/search/DeckSearch.js b/src/components/search/DeckSearch.js index aee988a..1fa3f56 100644 --- a/src/components/search/DeckSearch.js +++ b/src/components/search/DeckSearch.js @@ -12,7 +12,7 @@ import { useCardStore } from '../../context/CardContext/CardStore'; import { useTheme } from '@emotion/react'; import SearchForm from './SearchForm'; import DeckSearchCardGrid from '../grids/searchResultGrids/DeckSearchCardGrid'; -import CustomPagination from '../other/CustomPagination'; +import CustomPagination from '../reusable/CustomPagination'; const DeckSearch = ({ userDecks }) => { const [searchTerm, setSearchTerm] = useState(''); diff --git a/src/components/search/SearchBar.js b/src/components/search/SearchBar.js index d8d8338..5895198 100644 --- a/src/components/search/SearchBar.js +++ b/src/components/search/SearchBar.js @@ -2,8 +2,8 @@ import React from 'react'; import { Grid, Box, Typography, Container } from '@mui/material'; import { useCardStore } from '../../context/CardContext/CardStore'; import SearchButton from '../buttons/other/SearchButton'; -import CardNameInput from '../other/CardNameInput'; -import CustomSelector from '../other/CustomSelector'; +import CardNameInput from '../other/InputComponents/CardNameInput'; +import CustomSelector from '../other/InputComponents/CustomSelector'; import { useCombinedContext } from '../../context/CombinedProvider'; const initialState = { diff --git a/src/containers/PortfolioChartContainer.jsx b/src/containers/PortfolioChartContainer.jsx index 2f3348a..a91e87e 100644 --- a/src/containers/PortfolioChartContainer.jsx +++ b/src/containers/PortfolioChartContainer.jsx @@ -1,25 +1,18 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Box, Grid, Paper } from '@mui/material'; -import PortfolioChart from '../components/other/PortfolioChart'; -import TimeRangeSelector from '../components/other/TimeRangeSelector'; -import CollectionStatisticsSelector from '../components/other/CollectionStatisticsSelector'; -import UpdateStatusBox from '../components/other/UpdateStatusBox'; +import PortfolioChart from '../components/chart/PortfolioChart'; +import TimeRangeSelector from '../components/other/InputComponents/TimeRangeSelector'; +import CollectionStatisticsSelector, { + calculateStatistics, +} from '../components/other/InputComponents/CollectionStatisticsSelector'; +import UpdateStatusBox from '../components/other/InputComponents/UpdateStatusBox'; import { useTheme } from '@mui/material/styles'; import { useSocketContext } from '../context/SocketProvider'; import { useChartContext } from '../context/ChartContext/ChartContext'; import { useCollectionStore } from '../context/CollectionContext/CollectionContext'; import CollectionValueTracker from '../components/other/CollectionValueTracker'; -const paperStyle = { - elevation: 3, - borderRadius: 2, - p: 2, - height: '25vh', // Set the container height to 25vh - display: 'flex', - flexDirection: 'row', // Change to 'row' to fit selectors horizontally - justifyContent: 'space-between', // Distribute space evenly between selectors - alignItems: 'center', // Align items vertically in the center - gap: 2, // Set a gap between the selectors -}; +import { useCombinedContext } from '../context/CombinedProvider'; +import UpdateStatusBox2 from '../components/other/InputComponents/UpdateStatusBox2'; const paperChartStyle = { elevation: 3, borderRadius: 2, @@ -41,55 +34,70 @@ const selectorStyle = { const PortfolioChartContainer = ({ selectedCards, removeCard }) => { const theme = useTheme(); - const selectorStyle = { - // width: '100%', - // maxWidth: '100vw', - // maxWidth: '250px', // Match the width of the update box if needed - mb: theme.spacing(2), - }; - - const { socket } = useSocketContext(); + // const { socket } = useSocketContext(); + const { socket } = useCombinedContext(); const { timeRange } = useChartContext(); const { allCollections, selectedCollection } = useCollectionStore(); - const data = allCollections.map((collection) => { - return { - name: collection?.name, - data: collection?.chartData?.allXYValues, - }; - }); + const data = allCollections.map((collection) => ({ + data: collection?.currentChartDataSets2, + })); + const dataForStats = data[0]; + const stats = useMemo( + () => calculateStatistics(dataForStats, timeRange), + [dataForStats, timeRange] + ); + + const containerStyle = { + maxWidth: '100%', + // margin: 'auto', + padding: theme.spacing(2), + overflow: 'hidden', + }; + + const paperStyle = { + padding: theme.spacing(2), + marginBottom: theme.spacing(2), + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: 'auto', + boxShadow: theme.shadows[3], + borderRadius: theme.shape.borderRadius, + }; + + const gridItemStyle = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'space-between', + }; + return ( - + {/* Updaters Row */} - - - {/* */} + {/* */} + + + - - {/* */} + + {/* */} - - + + {/* Main Grid Container */} - + {/* Portfolio Chart Row */} - + { {/* Selectors Row */} - + @@ -110,6 +118,7 @@ const PortfolioChartContainer = ({ selectedCards, removeCard }) => { sx={selectorStyle} timeRange={timeRange} data={data} + stats={stats} />{' '} diff --git a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js index 7efbedd..b365cfd 100644 --- a/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js +++ b/src/containers/deckBuilderPageContainers/DeckBuilderContainer.js @@ -1,64 +1,6 @@ -// import React from 'react'; -// import { Grid, useMediaQuery } from '@mui/material'; -// import { useTheme } from '@emotion/react'; -// import DeckDisplay from '../../components/other/DeckDisplay'; -// import DeckSearch from '../../components/search/DeckSearch'; -// import { makeStyles } from '@mui/styles'; - -// const useStyles = makeStyles((theme) => ({ -// root: { -// overflow: 'auto', -// backgroundColor: '#f4f6f8', -// padding: theme.spacing(3), -// borderRadius: '10px', -// boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', -// width: '100%', // Add this to make sure the container takes full width -// }, -// searchGrid: { -// [theme.breakpoints.up('lg')]: { flexBasis: '35%' }, // Increased from 30% -// [theme.breakpoints.between('md', 'lg')]: { flexBasis: '50%' }, // Increased from 45% -// [theme.breakpoints.down('sm')]: { flexBasis: '50%' }, // Increased from 40% -// }, -// displayGrid: { -// flex: 1, -// padding: theme.spacing(1), -// }, -// })); - -// const DeckBuilderContainer = ({ userDecks }) => { -// const classes = useStyles(); -// const theme = useTheme(); -// const isMediumScreen = useMediaQuery(theme.breakpoints.between('sm', 'md')); -// const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); - -// const getXsValue = () => { -// if (isSmallScreen || isMediumScreen) return 5; // Increased from 4 -// return 3; -// }; - -// const getDisplayXsValue = () => { -// if (isSmallScreen || isMediumScreen) return 7; // Decreased from 8 -// return 9; -// }; - -// return ( -// -// -// -// -// -// -// -// -// ); -// }; - -// export default DeckBuilderContainer; import React from 'react'; import { Grid, useMediaQuery, useTheme } from '@mui/material'; -// Removed import { useTheme } from '@emotion/react'; - -import DeckDisplay from '../../components/other/DeckDisplay'; +import DeckDisplay from '../../components/grids/DeckDisplay'; import DeckSearch from '../../components/search/DeckSearch'; import { styled } from '@mui/system'; // Use @mui/system for Emotion styling @@ -91,22 +33,10 @@ const DeckBuilderContainer = ({ userDecks }) => { const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')); const getXsValue = () => { - // if (isSmallScreen || isMediumScreen) return 5; - // return 3; if (isLargeScreen) return 3; if (isMediumScreen) return 4; return 5; }; - // case 'xs': - // return 5; - // case 'sm': - // return 5; - // case 'md': - // return 5; - // case 'lg': - // return 7; - // }; - const getDisplayXsValue = () => { // if (isSmallScreen || isMediumScreen) return 7; // return 9; diff --git a/src/context/CollectionContext/CollectionContext.jsx b/src/context/CollectionContext/CollectionContext.jsx index 2544b84..df096f3 100644 --- a/src/context/CollectionContext/CollectionContext.jsx +++ b/src/context/CollectionContext/CollectionContext.jsx @@ -24,6 +24,7 @@ import { getPriceChange, constructPayloadWithDifferences, getCurrentChartDataSets, + calculateCollectionValue, } from './collectionUtility.jsx'; import moment from 'moment'; import { createNewDataSet } from './cardHelpers.jsx'; @@ -32,13 +33,65 @@ import { chunkPayload, sendChunks } from './ChunkPaylod.jsx'; export const CollectionContext = createContext(defaultContextValue); function transformPriceHistoryToXY(collectionPriceHistory) { - return collectionPriceHistory.map((entry) => ({ - x: entry.timestamp, // x represents the timestamp - y: entry.num, // y represents the numerical value - label: `Price at ${entry.timestamp}`, // label can be customized as needed + return collectionPriceHistory?.map((entry) => ({ + x: entry?.timestamp, // x represents the timestamp + y: entry?.num, // y represents the numerical value + label: `Price at ${entry?.timestamp}`, // label can be customized as needed })); } +const getAllCardPrices = (cards) => + cards.flatMap((card) => Array(card.quantity).fill(card.price)); + +function filterUniqueDataPoints(dataArray) { + const uniqueRecords = new Map(); + + dataArray?.forEach((item) => { + const key = `${item?.label}-${item?.x}-${item?.y}`; + if (!uniqueRecords.has(key)) { + uniqueRecords.set(key, item); + } + }); + + return Array.from(uniqueRecords.values()); +} + +function filterCollectionData(collection) { + if (!collection) return null; + + if (!collection?.chartData) { + console.warn('Collection data is missing chart data.'); + return collection; + } + collection.chartData.allXYValues = filterUniqueDataPoints( + collection?.chartData?.allXYValues + ); + collection.currentChartDataSets = filterUniqueDataPoints( + collection?.currentChartDataSets + ); + collection.currentChartDataSets2 = filterUniqueDataPoints( + collection?.currentChartDataSets2 + ); + + collection?.chartData?.datasets.forEach((dataset) => { + dataset?.data?.forEach((dataEntry) => { + dataEntry.xys = filterUniqueDataPoints(dataEntry?.xys); + }); + }); + + // Apply the filter function to 'xys' in 'chartData' + collection.chartData.xys = filterUniqueDataPoints(collection.chartData.xys); + + // If the 'cards' array contains structures with 'label', 'x', and 'y' properties + collection.cards.forEach((card) => { + if (card.chart_datasets) { + card.chart_datasets = filterUniqueDataPoints(card.chart_datasets); + } + }); + + return collection; +} + export const CollectionProvider = ({ children }) => { const [cookies] = useCookies(['user']); const [selectedCollection, setSelectedCollection] = useState( @@ -47,82 +100,79 @@ export const CollectionProvider = ({ children }) => { const [collectionData, setCollectionData] = useState(initialCollectionState); const [allCollections, setAllCollections] = useState([]); const [totalPrice, setTotalPrice] = useState(0); - const [allCardPrices, setAllCardPrices] = useState([]); + const [totalCost, setTotalCost] = useState(''); const [ updatedPricesFromCombinedContext, setUpdatedPricesFromCombinedContext, ] = useState([]); - const [xyData, setXyData] = useState([]); const [currentChartDataSets, setCurrentChartDataSets] = useState([]); const [currentChartDataSets2, setCurrentChartDataSets2] = useState([]); const [openChooseCollectionDialog, setOpenChooseCollectionDialog] = useState(false); const userId = cookies.user?.id; - // const currentChartDataSets = getCurrentChartDatasets( - // selectedCollection?.chartData - // ); - const totalCost = useMemo( - () => getTotalCost(selectedCollection), - [selectedCollection] - ); - // console.log('CURRENT CHART DATASETS:', currentChartDataSets); const lastFetchedTime = useRef(null); - const calculateTotalPrice = (collection) => - collection.cards.reduce( - (total, card) => total + (card.price || 0) * (card.quantity || 0), - 0 - ); const fetchAndSetCollections = useCallback(async () => { - // Throttle the fetch calls - const currentTime = Date.now(); - const fetchDelay = 60000; // 1 minute - if ( - lastFetchedTime.current && - currentTime - lastFetchedTime.current < fetchDelay - ) - return; - lastFetchedTime.current = currentTime; + const shouldFetch = () => { + const fetchDelay = 60000; // 1 minute + const currentTime = Date.now(); + return ( + !lastFetchedTime.current || + currentTime - lastFetchedTime.current >= fetchDelay + ); + }; + + if (!shouldFetch()) return; try { + lastFetchedTime.current = Date.now(); const response = await fetchWrapper( createApiUrl(`${userId}/collections`), 'GET' ); + const collections = response.data || []; - console.log('FETCHED COLLECTIONS:', response); - console.log('5. Fetched and set collections:', response); + console.log('FETCHED COLLECTIONS:', collections); - setAllCollections(response.data || []); - setCollectionData(response.data?.[0] || initialCollectionState); - setSelectedCollection(response.data?.[0] || initialCollectionState); + if (collections.length > 0) { + setAllCollections(collections); + setCollectionData(collections[0]); + setSelectedCollection(collections[0]); + } else { + console.warn('No collections found.'); + // Optionally, set a default or empty state if no collections are found + } } catch (error) { console.error(`Failed to fetch collections: ${error}`); } - }, [userId]); + }, [userId, setAllCollections, setCollectionData, setSelectedCollection]); + + const updateCollectionArray = (collections, newData) => { + const index = collections.findIndex((c) => c._id === newData?._id); + return index === -1 + ? [...collections, newData] + : collections.map((c) => (c._id === newData?._id ? newData : c)); + }; const updateCollectionData = useCallback( (newData, collectionType) => { - switch (collectionType) { - case 'allCollections': - setAllCollections((prev) => - prev.findIndex((c) => c._id === newData._id) === -1 - ? [...prev, newData] - : prev.map((c) => (c._id === newData._id ? newData : c)) - ); - break; - case 'selectedCollection': - setSelectedCollection(newData); - break; - case 'collectionData': - setCollectionData(newData); - break; - default: - console.warn( - '6. Unknown collection type for update:', - collectionType - ); + try { + switch (collectionType) { + case 'allCollections': + setAllCollections((prev) => updateCollectionArray(prev, newData)); + break; + case 'selectedCollection': + setSelectedCollection(newData); + break; + case 'collectionData': + setCollectionData(newData); + break; + default: + console.warn('Unknown collection type for update:', collectionType); + } + } catch (error) { + console.error('Error updating collection data:', error); } }, [setAllCollections, setSelectedCollection, setCollectionData] @@ -191,9 +241,6 @@ export const CollectionProvider = ({ children }) => { }; const getUpdatedCards = (activeCollection, cardUpdate, operation) => { - console.log('CARD UPDATE:', cardUpdate); - console.log('OPERATION', operation); - let cardsToUpdate; switch (operation) { @@ -204,266 +251,422 @@ export const CollectionProvider = ({ children }) => { cardsToUpdate = handleCardRemoval(activeCollection?.cards, cardUpdate); break; case 'update': - // Find the card by some unique identifier, e.g., id // eslint-disable-next-line no-case-declarations const cardIndex = activeCollection.cards.findIndex( (c) => c.id === cardUpdate.id ); if (cardIndex === -1) { console.error('Card not found in the collection.'); - return activeCollection.cards; // Return the unchanged cards array + return activeCollection.cards; } // eslint-disable-next-line no-case-declarations const existingCard = activeCollection.cards[cardIndex]; - // eslint-disable-next-line no-case-declarations - const updatedCard = { - ...existingCard, - latestPrice: cardUpdate.latestPrice, // assuming latestPrice is an object { num, timestamp, _id } - lastSavedPrice: cardUpdate.lastSavedPrice, // assuming lastSavedPrice is an object { num, timestamp, _id } - name: cardUpdate.name, - quantity: cardUpdate.quantity, - tag: cardUpdate.tag, - // Update priceHistory, ensure it's an array and append the new price - priceHistory: Array.isArray(existingCard.priceHistory) - ? [...existingCard.priceHistory, cardUpdate.priceHistory[0]] - : [cardUpdate.priceHistory[0]], - }; - console.log('UPDATED CARD:', updatedCard); - - // Replace the old card with the updated card - cardsToUpdate = [ - ...activeCollection.cards.slice(0, cardIndex), + const updatedPriceHistory = updatePriceHistory( + existingCard, + cardUpdate + ); + // eslint-disable-next-line no-case-declarations + const updatedCard = getUpdatedCard( + existingCard, + cardUpdate, + updatedPriceHistory, + activeCollection._id + ); + cardsToUpdate = replaceCardInArray( + activeCollection.cards, updatedCard, - ...activeCollection.cards.slice(cardIndex + 1), - ]; - console.log('UPDATED CARD:', updatedCard); - return cardsToUpdate; // Directly return the updated array of cards - + cardIndex + ); + break; default: console.error('Unsupported operation:', operation); - cardsToUpdate = activeCollection.cards; // Return the unchanged cards array + return activeCollection.cards; } - console.log('CARDS TO UPDATE:', cardsToUpdate); - return cardsToUpdate.map((existingCard) => { - console.log('EXISTING CARD BEFORE UPDATE:', existingCard); + return cardsToUpdate; + }; - // Calculate new card values - let cardPrice = null; - if (existingCard.price) { - cardPrice = existingCard.price; - } else { - cardPrice = existingCard.card_prices[0].tcgplayer_price; - } + function replaceCardInArray(cardsArray, newCard, index) { + return [ + ...cardsArray.slice(0, index), + newCard, + ...cardsArray.slice(index + 1), + ]; + } - console.log('EXISTING CARD PRICE:', cardPrice); - const computedPrice = - cardPrice * (cardUpdate.quantity || existingCard.quantity); - console.log('EXISTING CARD TOTALPRICE:', computedPrice); + function getUpdatedCard(card, update, priceHistory, collectionId) { + const cardPrice = determineCardPrice(card, update); + const newChartDataEntry = createChartDataEntry(totalPrice); + + return { + ...card, + price: cardPrice, + quantity: update.quantity || card.quantity, + collectionId: collectionId, + totalPrice: cardPrice * (update.quantity || card.quantity), + lastSavedPrice: update.lastSavedPrice, + latestPrice: update.latestPrice, + tag: 'monitored', + chart_datasets: [...(card.chart_datasets || []), newChartDataEntry], + priceHistory: priceHistory, + }; + } - // Generate chart data and price history - const newChartDataEntry = { - x: moment().format('YYYY-MM-DD HH:mm'), - y: computedPrice, - }; - const newPriceHistoryEntry = createPriceHistoryObject(computedPrice); - const valueOfMostRecentEntry = computedPrice; - // existingCard.chart_datasets?.[existingCard.chart_datasets?.length - 1] - // ?.data?.y - - // existingCard.chart_datasets?.[existingCard.chart_datasets?.length - 2] - // ?.data?.y; - console.log('VALUE OF MOST RECENT ENTRY:', computedPrice); - - const updatedCard = { - ...existingCard, - price: cardPrice, - totalPrice: computedPrice, - tag: 'monitored', - chart_datasets: [ - ...(existingCard.chart_datasets || []), - newChartDataEntry, - ], - priceHistory: - existingCard.priceHistory && - existingCard.priceHistory[existingCard.priceHistory.length - 1] !== - valueOfMostRecentEntry // Check if the new price history entry is different from the last one - ? [...existingCard.priceHistory, newPriceHistoryEntry] - : [newPriceHistoryEntry], - }; + function determineCardPrice(card, update) { + if (update?.latestPrice?.num) return update.latestPrice.num; + if (card.price) return card.price; + return card.card_prices[0].tcgplayer_price; + } - console.log('UPDATED CARD:', updatedCard); - return updatedCard; - }); - }; + function updatePriceHistory(card, update) { + const newPriceHistoryEntry = createPriceHistoryObject( + update.latestPrice.num + ); + const lastPriceHistoryEntry = + card.priceHistory[card.priceHistory.length - 1]; - const createPriceHistoryObject = (price) => ({ - timestamp: new Date().toISOString(), - num: price, - }); + if ( + !lastPriceHistoryEntry || + lastPriceHistoryEntry.num !== newPriceHistoryEntry.num + ) { + return [...card.priceHistory, newPriceHistoryEntry]; + } + return card.priceHistory; + } - const getUpdatedCollection = async ( + function createChartDataEntry(price) { + return { + x: moment().format('YYYY-MM-DD HH:mm'), + y: price, + }; + } + + function createPriceHistoryObject(price) { + return { + num: price, + timestamp: new Date(), + }; + } + + // Helper function to get updated collection data + const getUpdatedCollectionData = ( collectionWithCards, - cardUpdate, - operation + updatedTotalPrice, + newCollectionPriceHistoryObject, + updatedChartData, + updatedTotalQuantity, + updatedCards ) => { - const collectionId = selectedCollection._id || null; - if (collectionId) { - console.log('COLLECTION ID:', collectionId); + // Check for null or undefined collectionWithCards + if (!collectionWithCards) { + console.error('No collection data provided'); + return null; // or an appropriate default object } - const isCreating = !collectionId; - const method = isCreating ? 'POST' : 'PUT'; - const updatedTotalPrice = calculateTotalPrice(collectionWithCards); - const newCollectionPriceHistoryObject = - createPriceHistoryObject(updatedTotalPrice); - // UPDATE CARDS IN COLLECTION WITH CARDS ENDPOINT --------------------------- - console.log('CARD UPDATE:', cardUpdate); - const updatedCards = collectionWithCards.cards; - let cardsPayload = { - cards: updatedCards, + const { + allCardPrices = [], + description = '', + name = '', + // _id = '', + collectionPriceHistory = [], + cards = [], + } = collectionWithCards; + + return { + allCardPrices, + description, + name, + userId: userId, // Make sure 'userId' is defined in the scope + totalPrice: updatedTotalPrice || 0, + totalCost: updatedTotalPrice ? updatedTotalPrice.toString() : '0', + totalQuantity: cards.reduce((acc, card) => acc + (card.quantity || 0), 0), + quantity: cards.length, + // _id, + dailyPriceChange: + getPriceChange(currentChartDataSets2)[0]?.priceChange || '', + currentChartDataSets: filterUniqueDataPoints( + getCurrentChartDataSets(updatedChartData) + ), + currentChartDataSets2: filterUniqueDataPoints( + transformPriceHistoryToXY(collectionPriceHistory) + ), + collectionPriceHistory: [ + ...collectionPriceHistory, + newCollectionPriceHistoryObject, + ], }; - let cardsEndpoint = createApiUrl( - `${userId}/collections/${collectionWithCards._id}/updateCards` - ); - console.log( - `Sending ${method} request to ${cardsEndpoint} with payload:`, - cardsPayload - ); - let cardsResponse = await fetchWrapper(cardsEndpoint, method, cardsPayload); - console.log('Cards Update Response:', cardsResponse); - console.log('Cards Update Response Data:', cardsResponse.data); - console.log('Cards Update Response Message:', cardsResponse.message); - - // UPDATE CHARTS IN COLLECTION WITH CHARTS ENDPOINT -------------------------------- - const updatedChartData = { - ...selectedCollection.chartData, + }; + + const getFilteredChartData = (chartData, updatedTotalPrice) => { + const filteredChartData = { + ...chartData, + allXYValues: filterUniqueDataPoints(chartData?.allXYValues), + datasets: chartData?.datasets.map((dataset) => ({ + ...dataset, + data: dataset?.data.map((dataEntry) => ({ + ...dataEntry, + xys: filterUniqueDataPoints(dataEntry?.xys), + })), + })), + }; + return { + ...filteredChartData, allXYValues: [ - ...(selectedCollection.chartData?.allXYValues || []), + ...filteredChartData.allXYValues, { label: `Update - ${new Date().toISOString()}`, x: new Date().toISOString(), y: updatedTotalPrice, }, ], - datasets: [ - ...(selectedCollection.chartData?.datasets || []), - createNewDataSet(updatedTotalPrice, selectedCollection), - ], }; + }; - const testData = getUpdatedChartData(selectedCollection, updatedTotalPrice); - console.log('TEST DATA:', testData); - let chartDataPayload = { - chartData: updatedChartData, - }; - let chartDataEndpoint = createApiUrl( - `${userId}/collections/${collectionWithCards._id}/updateChartData` + const removeCardsFromCollection = async (userId, collectionId, cardIds) => { + const endpoint = `/api/${userId}/collections/${collectionId}/removeCards`; + const method = 'POST'; + const payload = { cardIds }; + + try { + const response = await fetchWrapper(endpoint, method, payload); + return response; // The response contains the updated cards list + } catch (error) { + console.error('Error removing cards:', error); + throw error; + } + }; + + const getUpdatedCollection = async ( + collectionWithCards, // updated cards + cardUpdate, // updated card + operation, + userId + ) => { + const collectionId = collectionWithCards?._id || collectionData?._id; + if (!collectionId) { + console.error('Collection ID is missing.'); + return; + } + + const cardExists = collectionWithCards.cards.some( + (card) => card.id === cardUpdate.id ); - console.log( - `Sending ${method} request to ${chartDataEndpoint} with payload:`, - chartDataPayload + + // Determine the method and endpoint based on operation and card existence + const method = + operation === 'remove' ? 'DELETE' : cardExists ? 'PUT' : 'POST'; + const endpointSuffix = + operation === 'remove' ? 'removeCards' : 'updateCards'; + const endpoint = createApiUrl( + `${userId}/collections/${collectionId}/${endpointSuffix}` ); - let chartDataResponse = await fetchWrapper( - chartDataEndpoint, - method, - chartDataPayload + + console.log('CARDS BEFORE: ', collectionWithCards); + const updatedCards = getUpdatedCards( + collectionWithCards, + cardUpdate, + operation ); - console.log('Chart Data Update Response:', chartDataResponse); - console.log('Chart Data Update Response Data:', chartDataResponse.data); - console.log( - 'Chart Data Update Response Message:', - chartDataResponse.message + console.log('CARDS AFTER: ', updatedCards); + + const updatedTotalPrice = calculateCollectionValue(updatedCards); + setTotalPrice(updatedTotalPrice); + + // const updatedTotalPrice = updatedCards.reduce( + // (total, card) => total + card.price * card.quantity, + // 0 + // ); + const updatedTotalQuantity = updatedCards.reduce( + (total, card) => total + card.quantity, + 0 ); + const newCollectionPriceHistoryObject = + createPriceHistoryObject(updatedTotalPrice); - // UPDATE COLLECTION WITH COLLECTION ENDPOINT -------------------------------- - const updatedCollection = { - // ...selectedCollection, - allCardPrices: updatedCards.flatMap((card) => - Array(card.quantity).fill(card.price) - ), - description: collectionWithCards.description, - name: collectionWithCards.name, - userId: userId, - totalPrice: updatedTotalPrice, - totalCost: updatedTotalPrice.toString(), - totalQuantity: updatedCards.reduce((acc, card) => acc + card.quantity, 0), - quantity: updatedCards.length, - _id: collectionId, - chartData: updatedChartData, - cards: updatedCards, - dailyPriceChange: getPriceChange( - selectedCollection?.collectionPriceHistory - ), - currentChartDataSets: getCurrentChartDataSets(updatedChartData), - currentChartDataSets2: transformPriceHistoryToXY( - selectedCollection.collectionPriceHistory - ), - collectionPriceHistory: selectedCollection.collectionPriceHistory - ? [ - ...selectedCollection.collectionPriceHistory, - newCollectionPriceHistoryObject, - ] - : [newCollectionPriceHistoryObject], - }; - // Check if creating a new collection or updating an existing one - const endpoint = createApiUrl( - `${userId}/collections/${collectionId || ''}` + let cardsResponse; + // if (operation === 'remove') { + // const cardIds = updatedCards.map((card) => card.id); + // cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); + // } else { + // const cardsPayload = { cards: updatedCards }; + // cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); + // } + const cardsPayload = { cards: updatedCards }; + cardsResponse = await fetchWrapper(endpoint, method, cardsPayload); + const { cardMessage } = cardsResponse; + + const updatedChartData = getFilteredChartData( + collectionWithCards.chartData, + updatedTotalPrice ); + const chartDataPayload = { chartData: updatedChartData }; + const chartDataEndpoint = createApiUrl( + `${userId}/collections/${collectionId}/updateChartData` + ); + const chartDataResponse = await fetchWrapper( + chartDataEndpoint, + 'PUT', + chartDataPayload + ); + + const { chartMessage } = chartDataResponse; - const { nonMatchingKeys, payload } = constructPayloadWithDifferences( + // const updatedTotalPrice = calculateCollectionValue(selectedCollection); + // console.log('NEW VALUE:', newVal); + + // Prepare the updated collection data + const updatedCollection = getUpdatedCollectionData( selectedCollection, - updatedCollection, - true - ); // Assume constructPayload does the necessary processing - console.log('NON-MATCHING KEYS:', nonMatchingKeys); - console.log( - `Sending ${method} request to ${endpoint} with payload:`, - payload + updatedTotalPrice, + newCollectionPriceHistoryObject, + // updatedChartData, + updatedTotalQuantity + // updatedCards ); - setCurrentChartDataSets2( - transformPriceHistoryToXY(selectedCollection.collectionPriceHistory) - ); - // Break the payload into chunks if necessary - const chunks = chunkPayload(payload); - console.log( - `Sending ${method} request to ${endpoint} with ${chunks.length} chunks.` + // Update the collection + const collectionEndpoint = createApiUrl( + `${userId}/collections/${collectionId}` ); + const collectionResponse = await fetchWrapper(collectionEndpoint, 'PUT', { + updatedCollection, + }); + + const { collectionMessage } = collectionResponse; + + console.log('********** [--------------------] **********'); + console.log('********** [CARDS] **********'); + console.log(`********** [${cardMessage}] **********`); + console.log(`********** [${cardsResponse.cards}] **********`); + console.log('********** [--------------------] **********'); + console.log('********** [CHARTS] **********'); + console.log(`********** [${chartMessage}] **********`); + console.log(`********** [${chartDataResponse.chartData}] **********`); + console.log('********** [--------------------] **********'); + console.log('********** [COLLECTION] **********'); + console.log(`********** [${collectionMessage}] **********`); + console.log(`********** [${collectionResponse.collectionData}] **********`); + console.log('********** [--------------------] **********'); + + // Restructure the collection object + // Optionally, update context or state with the new collection data + // updateCollectionData(collectionResponse.collectionData, 'allCollections'); + // updateCollectionData( + // collectionResponse.collectionData, + // 'selectedCollection' + // ); + // updateCollectionData(collectionResponse.collectionData, 'collectionData'); + setTotalPrice(calculateCollectionValue(updatedCards)); + const restructuredCollection = { + ...collectionResponse.collectionData, + cards: cardsResponse.cards, + chartData: chartDataResponse.chartData, + }; - const response = await fetchWrapper(endpoint, method, payload); - console.log('RESPONSE', response); - const updatedCollectionPostServer = response.data; + console.log('RESTRUCTURED COLLECTION:', restructuredCollection); + // Return the updated collection data along with responses for cards and chart data + return { + restructuredCollection, + }; + }; - // const combinedCollectionUpdate = { - // ...updatedCard + const updateCollectionDetails = async (updatedInfo, userId, collectionId) => { + const { name, description } = updatedInfo; + if (selectedCollection && collectionId) { + const updatedCollection = { + ...selectedCollection, + name, + description, + }; - console.log('UPDATED COLLECTION POST SERVER:', updatedCollectionPostServer); - updateCollectionData(updatedCollectionPostServer, 'allCollections'); - updateCollectionData(updatedCollectionPostServer, 'collectionData'); - updateCollectionData(updatedCollectionPostServer, 'selectedCollection'); + // Update in the state first + setSelectedCollection(updatedCollection); + setAllCollections((prevCollections) => + prevCollections.map((collection) => + collection._id === collectionId ? updatedCollection : collection + ) + ); - return updatedCollection; + try { + // Update the collection in the backend + const collectionEndpoint = createApiUrl( + `${userId}/collections/${collectionId}` + ); + const collectionResponse = await fetchWrapper( + collectionEndpoint, + 'PUT', + { + updatedCollection, + } + ); + + const { collectionMessage } = collectionResponse; + console.log(collectionMessage); + + // Optionally handle the response here, e.g., update the state with the response data if necessary + } catch (error) { + console.error('Error updating collection details:', error); + // Handle error, e.g., revert state changes or display error message to the user + } + } else { + console.error('Selected collection or collection ID is missing.'); + } }; - const handleCardOperation = async (card, operation) => { + const handleCardOperation = async ( + card, + operation, + selectedCollection, + userId, + allCollections + ) => { if (!card) { console.error('Card is undefined.', card); return; } - const updatedCards = getUpdatedCards(selectedCollection, card, operation); - console.log('UPDATED CARDS:', updatedCards); - const collectionWithCards = { ...selectedCollection, cards: updatedCards }; - console.log('COLLECTION WITH CARDS:', collectionWithCards); + // Check if selectedCollection is defined, if not set to first collection in allCollections + if (!selectedCollection || !selectedCollection._id) { + if (allCollections && allCollections.length > 0) { + selectedCollection = allCollections[0]; + } else { + console.error('Selected collection or allCollections is empty.'); + return; // Stop execution if no valid collection is available + } + } + + const collectionId = selectedCollection._id; + + const updatedCards = getUpdatedCards( + selectedCollection, + card, + operation, + collectionId + ); + + const collectionWithCards = { + ...selectedCollection, + cards: updatedCards, + _id: selectedCollection._id, + }; + const updatedCollection = await getUpdatedCollection( collectionWithCards, card, - operation + operation, + userId ); - console.log('UPDATED COLLECTION POST OP HANDLING:', updatedCollection); - updateCollectionData(updatedCollection, 'selectedCollection'); + + if (updatedCollection) { + console.log('UPDATED COLLECTION:', updatedCollection); + updateCollectionData(updatedCollection, 'allCollections'); + updateCollectionData(updatedCollection, 'selectedCollection'); + updateCollectionData(updatedCollection, 'collectionData'); + } else { + console.error('Failed to update collection.'); + } return updatedCollection; }; @@ -475,24 +678,41 @@ export const CollectionProvider = ({ children }) => { collectionData, totalCost, totalPrice, + totalQuantity: selectedCollection?.totalQuantity || 0, allCardPrices: selectedCollection?.allCardPrices || [], xys: xyData || [], openChooseCollectionDialog, - setAllCardPrices, + calculateCollectionValue, + // setAllCardPrices, + setTotalPrice, setXyData, setUpdatedPricesFromCombinedContext, setOpenChooseCollectionDialog, - calculateTotalPrice: () => getCardPrice(selectedCollection), - getTotalPrice: () => calculateTotalPrice(allCardPrices), + getTotalPrice2: () => getCardPrice(selectedCollection), + getTotalPrice: () => calculateCollectionValue(selectedCollection), + getNewTotalPrice: (values) => calculateCollectionValue(values), getTotalCost: () => getTotalCost(selectedCollection), getCardQuantity: (cardId) => selectedCollection?.cards?.find((c) => c?.id === cardId)?.quantity || 0, createUserCollection: (userId, newCollectionInfo, name, description) => createUserCollection(userId, newCollectionInfo, name, description), removeCollection, - addOneToCollection: (card) => handleCardOperation(card, 'add'), - removeOneFromCollection: (card) => handleCardOperation(card, 'remove'), + addOneToCollection: (card) => + handleCardOperation(card, 'add', selectedCollection, userId), + removeOneFromCollection: (card) => + handleCardOperation(card, 'remove', selectedCollection, userId), updateOneFromCollection: (card) => handleCardOperation(card, 'update'), + updateCollection: (collectionWithCards, card, operation, userId) => + getUpdatedCollection(collectionWithCards, card, operation, userId), + updateCollectionState: (collection) => updateCollectionData(collection), + updateCollectionDetails: (updatedInfo, userId, collectionId) => + updateCollectionDetails(updatedInfo, userId, collectionId), + // updateCollectionDetails: (collection) => { + // console.log('UPDATING COLLECTION DETAILS:', collection); + // updateCollectionData(collection, 'allCollections'); + // updateCollectionData(collection, 'selectedCollection'); + // updateCollectionData(collection, 'collectionData'); + // } fetchAllCollectionsForUser: fetchAndSetCollections, fetchAllCollections: fetchAndSetCollections, setSelectedCollection, @@ -505,6 +725,7 @@ export const CollectionProvider = ({ children }) => { console.log('CONTEXT STATE:', { totalCost, totalPrice, + totalQuantity: selectedCollection?.totalQuantity || 0, selectedCollection, allCollections, collectionData, @@ -522,19 +743,51 @@ export const CollectionProvider = ({ children }) => { }, [userId]); useEffect(() => { - if (selectedCollection.chartData) + if (selectedCollection?.chartData) setCurrentChartDataSets( - getCurrentChartDataSets(selectedCollection.chartData) + getCurrentChartDataSets(selectedCollection?.chartData) ); console.log('CURRENT CHART DATASETS:', currentChartDataSets); }, [selectedCollection]); useEffect(() => { - if (selectedCollection.cards) { - setTotalPrice(calculateTotalPrice(selectedCollection)); + if (selectedCollection?.cards) { + setTotalPrice(calculateCollectionValue(selectedCollection)); + setTotalCost(getTotalCost(selectedCollection)); } - console.log('TOTAL PRICE:', totalPrice); - }, [selectedCollection]); + }, [selectedCollection, setTotalPrice]); + + useEffect(() => { + if (allCollections?.length === 0 || allCollections === undefined) { + fetchAndSetCollections(); + } + }, [allCollections, fetchAndSetCollections]); + + // setCurrentChartDataSets2( + // transformPriceHistoryToXY(selectedCollection?.chartData) + // ); + // console.log('CURRENT CHART DATASETS 2:', currentChartDataSets2); + // } + + useEffect(() => { + if (selectedCollection === null || selectedCollection === undefined) { + setSelectedCollection(allCollections?.[0]); + } + }, [userId]); + + // setTotalPrice(calculateTotalPrice(selectedCollection)); + // console.log('TOTAL PRICE:', totalPrice); + // }, [selectedCollection, setTotalPrice]); + + // useEffect(() => { + // const updatedSelectedCollection + // { + // ...selectedCollection, + + // } + + // getUpdatedCollection + // }, [setAllCardPrices, setTotalPrice]); return ( diff --git a/src/context/CollectionContext/checkServerForUpdates.jsx b/src/context/CollectionContext/checkServerForUpdates.jsx new file mode 100644 index 0000000..b622890 --- /dev/null +++ b/src/context/CollectionContext/checkServerForUpdates.jsx @@ -0,0 +1,66 @@ +// Client-side JavaScript (e.g., in a React component or similar) +import axios from 'axios'; +import { useContext, useState } from 'react'; +import { CollectionContext } from './CollectionContext'; +import { useCombinedContext } from '../CombinedProvider'; +import { createApiUrl, fetchWrapper } from './collectionUtility'; +import { useCookies } from 'react-cookie'; + +async function checkServerForUpdates() { + const { socket } = useCombinedContext(); + const [updateResponse, setUpdateResponse] = useState(null); + try { + // Send an API request to check for updates + socket.emit('UPDATE_REQUEST_CHECK', { + message: 'Checking for updates...', + }); + + socket.on('UPDATE_CHECK_INITIAL_RESPONSE', (response) => { + console.log('UPDATE_CHECK_INITIAL_RESPONSE', response); + setUpdateResponse(response.message); + }); + + socket.on('UPDATES_RECEIVED', (response) => { + console.log('UPDATE_CHECK_RESPONSE', response); + if (response.hasUpdates) { + // If there are updates, call your client-side update function + updateCollectionClientSide(response.data); + } + setUpdateResponse(response.message); + }); + } catch (error) { + console.error('Error checking for updates:', error); + } +} +setInterval(checkServerForUpdates, 300000); // Check every 5 minutes + +const updateCollectionClientSide = async (response) => { + const { collection, setCollection, updateCollectionState } = + useContext(CollectionContext); + try { + updateCollectionState(response); + // Now, send an API request to save the updated collection data + await saveUpdatedCollectionData(response); + } catch (error) { + console.error('Error updating collection client-side:', error); + } +}; + +const saveUpdatedCollectionData = async (response) => { + const [cookies] = useCookies(['user']); + const userId = cookies.user?.id; + const collectionId = response._id; + try { + const collectionEndpoint = createApiUrl( + `${userId}/collections/${collectionId || ''}` + ); + const collectionResponse = await fetchWrapper(collectionEndpoint, 'PUT', { + response, + }); + + console.log('collectionResponse', collectionResponse); + console.log('Collection data saved successfully'); + } catch (error) { + console.error('Error saving updated collection data:', error); + } +}; diff --git a/src/context/CollectionContext/collectionUtility.jsx b/src/context/CollectionContext/collectionUtility.jsx index a7756f1..3b2858f 100644 --- a/src/context/CollectionContext/collectionUtility.jsx +++ b/src/context/CollectionContext/collectionUtility.jsx @@ -387,8 +387,11 @@ const defaultContextValue = { setUpdatedPricesFromCombinedContext: () => {}, // eslint-disable-next-line @typescript-eslint/no-empty-function setOpenChooseCollectionDialog: () => {}, + updateCollection: () => {}, calculateTotalPrice: () => {}, getTotalCost: () => {}, + getNewTotalPrice: () => {}, + getTotalPrice: () => {}, createUserCollection: () => {}, removeCollection: () => {}, fetchAllCollectionsForUser: () => {}, @@ -613,40 +616,119 @@ function getCurrentChartDataSets(chartData) { return currentChartDataSets; } -const getPriceChange = (collectionPriceHistory) => { +const calculateCollectionValue = (cards) => { + if (!cards?.cards && !Array.isArray(cards) && !cards?.name) { + console.warn('Invalid or missing collection', cards); + return 0; + } + if (cards?.tag === 'new') { + return 0; + } + if (cards?.cards && Array.isArray(cards?.cards)) { + return cards?.cards.reduce((totalValue, card) => { + const cardPrice = card?.price || 0; + const cardQuantity = card?.quantity || 0; + return totalValue + cardPrice * cardQuantity; + }, 0); + } + + return cards.reduce((totalValue, card) => { + const cardPrice = card.price || 0; + const cardQuantity = card.quantity || 0; + return totalValue + cardPrice * cardQuantity; + }, 0); +}; + +// const getPriceChange = (collectionPriceHistory) => { +// if ( +// !Array.isArray(collectionPriceHistory) || +// collectionPriceHistory.length === 0 +// ) { +// console.warn('Invalid or empty price history', collectionPriceHistory); +// return 'n/a'; +// } + +// const mostRecentPrice = +// collectionPriceHistory[collectionPriceHistory.length - 1]?.num; +// const currentDate = new Date(); + +// // Get the first price from the last 24 hours +// const firstPriceFromLastDay = collectionPriceHistory +// .slice() +// .reverse() +// .find((priceHistory) => { +// const historyDate = new Date(priceHistory.timestamp); +// return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours +// })?.num; + +// if (mostRecentPrice && firstPriceFromLastDay) { +// const priceChange = +// ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; +// console.log( +// `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` +// ); +// return priceChange.toFixed(2); +// } else { +// console.error('Could not calculate price change due to missing data'); +// return null; +// } +// }; + +function getPriceChange(currentChartDataSets2) { if ( - !Array.isArray(collectionPriceHistory) || - collectionPriceHistory.length === 0 + !Array.isArray(currentChartDataSets2) || + currentChartDataSets2.length === 0 ) { - console.warn('Invalid or empty price history', collectionPriceHistory); - return 'n/a'; + console.warn('Invalid or empty chart data sets provided'); + return []; } - const mostRecentPrice = - collectionPriceHistory[collectionPriceHistory.length - 1]?.num; - const currentDate = new Date(); - - // Get the first price from the last 24 hours - const firstPriceFromLastDay = collectionPriceHistory - .slice() - .reverse() - .find((priceHistory) => { - const historyDate = new Date(priceHistory.timestamp); - return currentDate - historyDate <= 24 * 60 * 60 * 1000; // less than 24 hours - })?.num; - - if (mostRecentPrice && firstPriceFromLastDay) { - const priceChange = - ((mostRecentPrice - firstPriceFromLastDay) / firstPriceFromLastDay) * 100; - console.log( - `Price change over the last 24 hours is: ${priceChange.toFixed(2)}%` - ); - return priceChange.toFixed(2); - } else { - console.error('Could not calculate price change due to missing data'); - return null; + const sortedData = currentChartDataSets2 + .filter((dataPoint) => dataPoint && dataPoint.x && dataPoint.y != null) // Filter out invalid data points + .sort((a, b) => new Date(a.x) - new Date(b.x)); + + if (sortedData.length === 0) { + console.error('No valid chart data points after filtering'); + return []; } -}; + + const latestDataPoint = sortedData[sortedData.length - 1]; + const latestTime = new Date(latestDataPoint.x).getTime(); + const twentyFourHoursAgo = latestTime - 24 * 60 * 60 * 1000; + + let closestIndex = -1; + let closestTimeDifference = Number.MAX_SAFE_INTEGER; + + for (let i = 0; i < sortedData.length - 1; i++) { + const time = new Date(sortedData[i].x).getTime(); + const timeDifference = Math.abs(time - twentyFourHoursAgo); + + if (timeDifference < closestTimeDifference) { + closestTimeDifference = timeDifference; + closestIndex = i; + } + } + + if (closestIndex !== -1) { + const pastPrice = sortedData[closestIndex].y; + const priceChange = latestDataPoint.y - pastPrice; + const percentageChange = ((priceChange / pastPrice) * 100).toFixed(2); + + return [ + { + startDate: sortedData[closestIndex].x, + lowPoint: pastPrice.toFixed(2), + highPoint: latestDataPoint?.y?.toFixed(2), + endDate: latestDataPoint?.x, + priceChange: priceChange.toFixed(2), + percentageChange: `${percentageChange}%`, + priceIncreased: priceChange > 0, + }, + ]; + } + + return []; +} const getUpdatedChartData = (collection, newPrice) => { const newXYValue = { @@ -713,7 +795,6 @@ const updateCardInCollection = (cards, cardToUpdate) => { }; const getCardPrice = (collection) => - console.log('CARD:', collection) || parseFloat(collection?.cards?.card_prices?.[0]?.tcgplayer_price || 0); module.exports = { @@ -749,4 +830,5 @@ module.exports = { updateCardInCollection, canMakeRequest, updateLastRequestTime, + calculateCollectionValue, }; diff --git a/src/context/CombinedProvider.jsx b/src/context/CombinedProvider.jsx index bc0d6ba..f7fa7d1 100644 --- a/src/context/CombinedProvider.jsx +++ b/src/context/CombinedProvider.jsx @@ -17,10 +17,10 @@ const initialState = { allData: {}, data: {}, messageTest: {}, - chartData: {}, + // chartData: {}, existingChartData: {}, collectionData: {}, - currentChartData: {}, + // currentChartData: {}, allCollectionsUpdated: {}, simData: {}, allCollectionData: {}, @@ -82,37 +82,44 @@ const filterDuplicatePrices = (data) => { }); }; -function processCardPrices(cardPrices) { - if (!cardPrices || !cardPrices.data || !Array.isArray(cardPrices.data.data)) { - console.error('Invalid cardPrices data structure.'); - return; - } +// function processCardPrices(cardPrices, selectedCollection) { +// if (!cardPrices || !cardPrices.data || !Array.isArray(cardPrices.data.data)) { +// console.error('Invalid cardPrices data structure.'); +// return; +// } - const priceArray = []; - let totalPrice = 0; +// console.log('Card prices retrieved:', cardPrices); +// const priceArray = []; +// let totalPrice = 0; - cardPrices.data.data.forEach((card) => { - const { latestPrice, quantity } = card; +// cardPrices.data.data.forEach((card) => { +// const { latestPrice, quantity } = card; - if (!latestPrice || !quantity) { - console.error(`Missing price or quantity for card ID: ${card.id}`); - return; - } +// if (!latestPrice || !quantity) { +// console.error(`Missing price or quantity for card ID: ${card.id}`); +// return; +// } - for (let i = 0; i < quantity; i++) { - priceArray.push(latestPrice.num); - totalPrice += latestPrice.num; - } - }); +// for (let i = 0; i < quantity; i++) { +// priceArray.push(latestPrice.num); +// totalPrice += latestPrice.num; +// } +// }); + +// const filteredCards = cardPrices.data.data.filter((card) => { +// const cardIds = selectedCollection?.cards?.map((card) => card.id); +// return cardIds?.includes(card.id); +// }); - console.log('Price Array:', priceArray); - console.log('Total Price:', totalPrice.toFixed(2)); +// console.log('Price Array:', priceArray); +// console.log('Total Price:', totalPrice.toFixed(2)); +// // console.log('Filtered Cards:', filteredCards); - // Save priceArray and totalPrice as needed - // For example, you might want to set them to your application's state +// // Save priceArray and totalPrice as needed +// // For example, you might want to set them to your application's state - return { priceArray, totalPrice: totalPrice.toFixed(2) }; -} +// return { priceArray, totalPrice: totalPrice.toFixed(2) }; +// } const isEmpty = (obj) => { return ( @@ -122,20 +129,38 @@ const isEmpty = (obj) => { }; const validateData = (data, eventName, functionName) => { - const dataType = typeof data; + const dataType = typeof data || data.data || data.data.data || data.message; console.log( - `[SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}` + '----------------------------------------------------------------------------------------------------' + ); + console.log( + `| [SUCCESS] Received data of type: ${dataType} in ${functionName} triggered by event: ${eventName}] |` + ); + console.log( + '----------------------------------------------------------------------------------------------------' ); if (data === null || data === undefined) { + console.log( + '----------------------------------------------------------------------------------------------------' + ); console.warn( `[Warning] Received null or undefined data in ${functionName} triggered by event: ${eventName}` ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); return false; } - if (isEmpty(data)) { + if (isEmpty(data) && isEmpty(data?.data) && isEmpty(data?.data?.data)) { + console.log( + '----------------------------------------------------------------------------------------------------' + ); console.error( `[Error] Received empty data object or array in ${functionName} triggered by event: ${eventName}` ); + console.log( + '----------------------------------------------------------------------------------------------------' + ); return false; } return true; @@ -148,9 +173,12 @@ export const CombinedProvider = ({ children }) => { const { selectedCollection, allCollections, + getNewTotalPrice, updateOneFromCollection, + updateCollection, processAndUpdateCardPrices, setAllCardPrices, + setTotalPrice, setAllCollections, updatedPricesFromCombinedContext, officialCollectionDatasets, @@ -164,6 +192,8 @@ export const CombinedProvider = ({ children }) => { (data) => { setState((prev) => { let newData; + + // validateData(data, key, 'createStateUpdaterFunction'); if (Array.isArray(data)) { newData = [...data]; } else if (typeof data === 'object' && data !== null) { @@ -232,114 +262,349 @@ export const CombinedProvider = ({ children }) => { } }, [state.eventsTriggered]); // ----------- XXX ----------- - const listOfMonitoredCards = useMemo(() => { - const cards = allCollections?.flatMap((collection) => collection?.cards); - if (!cards) return []; - const uniqueCards = Array.from(new Set(cards.map((card) => card?.id))).map( - (id) => cards?.find((card) => card?.id === id) + const generateListOfMonitoredCards = (allCollections) => { + if (!allCollections) return []; + + // // Ensure cardPrices is an array + // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; + + // // Flatten all cards from all collections, including collection ID + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); + const cardsWithCollectionId = allCollections.flatMap((collection) => + collection?.cards?.map((card) => ({ + ...card, + collectionId: collection._id, + })) ); - let updatedPrices = null; - if (state.cardPrices?.updatedPrices) { - updatedPrices = state.cardPrices?.updatedPrices; - } - if (uniqueCards && uniqueCards.length) { - return uniqueCards?.map((card) => { - const updatedPriceInfo = updatedPrices?.[card?.id]; - return { - _id: card?._id, // Assuming each card has a unique identifier - id: card?.id, - tag: 'monitored', - name: card?.name, - quantity: card?.quantity, - latestPrice: { - num: updatedPriceInfo ? updatedPriceInfo.num : card?.price, - timestamp: updatedPriceInfo - ? updatedPriceInfo.timestamp - : new Date().toISOString(), - // Add _id field for latestPrice if available - }, - lastSavedPrice: { - num: card?.price, - timestamp: new Date().toISOString(), // Modify this based on where you get the last saved timestamp - // Add _id field for lastSavedPrice if available - }, - priceHistory: card?.priceHistory || [], // Assuming priceHistory is part of the original card object - __v: card?.__v, // Version key, if applicable - }; - }); - } - }, [allCollections, state.cardPrices?.updatedPrices]); + const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); - // ----------- SOCKET EVENT HANDLERS ----------- + return Array.from(uniqueCardIds).map((id) => { + const originalCard = cardsWithCollectionId.find((card) => card.id === id); - const safeEmit = useCallback( - (event, data) => { - try { - if (!validateData(data, event, 'safeEmit')) { - throw new Error(`Invalid data emitted for event: ${event}`); - } - if (socket) { - socket.emit(event, data); - console.log(`[Info] Emitted event: ${event}`); - } else { - console.warn('Socket is not connected. Cannot emit event:', event); - } - } catch (error) { - console.error(`[Error] Failed to emit event: ${event}`, error); - setDataFunctions.error({ - message: error.message, - source: 'safeEmit', - }); + return { + ...originalCard, + priceHistory: originalCard.priceHistory || [], + }; + }); + }; + const updateCardPricesInList = (listOfMonitoredCards, cardPrices) => { + return listOfMonitoredCards.map((originalCard) => { + const updatedCardInfo = + cardPrices.find((price) => price.id === originalCard.id) || {}; + + // If latestPrice is different, update lastSavedPrice and priceHistory + if (updatedCardInfo.latestPrice?.num !== originalCard.latestPrice?.num) { + return { + ...originalCard, + ...updatedCardInfo, + quantity: originalCard.quantity, + price: updatedCardInfo.latestPrice?.num || originalCard.price, + lastSavedPrice: + updatedCardInfo.lastSavedPrice || + updatedCardInfo.priceHistory[ + updatedCardInfo.priceHistory.length - 1 + ], + priceHistory: [ + ...originalCard.priceHistory, + updatedCardInfo.latestPrice, + ], + }; } - }, - [socket] + + return originalCard; + }); + }; + const listOfMonitoredCards = useMemo( + () => generateListOfMonitoredCards(allCollections), + [allCollections] ); - const safeOn = useCallback( - (event, handler) => { - const wrapper = (data) => { - try { - if (!validateData(data, event, handler.name)) { - throw new Error(`Invalid data received for event: ${event}`); - } // Add this line to validate the data received - // console.log(`[Info] Handling event: ${event}`); - handler(data); - } catch (error) { - console.error(`[Error] Failed to handle event: ${event}`, error); - setDataFunctions.error({ message: error.message, source: event }); - } - }; + // const listOfMonitoredCards = useMemo(() => { + // if (!allCollections) return []; - socket.on(event, wrapper); // Add this line to register the event listener + // const cardPrices = Array.isArray(state.cardPrices) + // ? state.cardPrices + // : [state.cardPrices]; - return () => { - socket.off(event, wrapper); // Add this line to unregister the event listener when the component unmounts - }; - }, - [socket] - ); + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); - const mergeUpdates = (currentArray, updates) => { - const updatedArray = [...currentArray]; - updates.forEach((update) => { - const index = updatedArray.findIndex((item) => item.id === update.id); - if (index !== -1) { - updatedArray[index] = { ...updatedArray[index], ...update }; - } else { - updatedArray.push(update); - } - }); - return updatedArray; - }; + // const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); + + // return Array.from(uniqueCardIds).map((id) => { + // const originalCard = cardsWithCollectionId.find((card) => card.id === id); + // const updatedCardInfo = cardPrices.find((price) => price.id === id) || {}; + + // const latestPrice = + // updatedCardInfo.latestPrice || originalCard.latestPrice; + // const lastSavedPrice = + // updatedCardInfo.lastSavedPrice || originalCard.lastSavedPrice; + + // return { + // ...originalCard, + // ...updatedCardInfo, + // latestPrice, + // lastSavedPrice, + // price: latestPrice?.num || originalCard.price, + // priceHistory: updatedCardInfo.priceHistory || originalCard.priceHistory, + // }; + // }); + // }, [allCollections, state.cardPrices]); + + // const updateCardPricesState = (currentCardPrices, updatedCardsList) => { + // // Ensure both currentCardPrices and updatedCardsList are arrays + // if (!Array.isArray(currentCardPrices)) currentCardPrices = []; + // if (!Array.isArray(updatedCardsList)) updatedCardsList = []; + + // // Create a map for easy lookup of current card prices by ID + // const currentCardPricesMap = new Map( + // currentCardPrices.map((card) => [card.id, card]) + // ); - const handleStatusUpdateCharts = (newData) => { - const { updates } = newData.data; - console.log('[STATUS_UPDATE_CHARTS] Data:', updates); - const updatedList = mergeUpdates(state.listOfSimulatedCards, updates); - setDataFunctions.listOfSimulatedCards(updatedList); + // // Update the card prices with new data from updatedCardsList + // return updatedCardsList.map((updatedCard) => { + // const currentCardPrice = currentCardPricesMap.get(updatedCard.id) || {}; + + // return { + // ...currentCardPrice, + // latestPrice: updatedCard.latestPrice || currentCardPrice.latestPrice, + // lastSavedPrice: + // updatedCard.lastSavedPrice || currentCardPrice.lastSavedPrice, + // price: updatedCard.latestPrice?.num || currentCardPrice.price, + // priceHistory: updatedCard.priceHistory || currentCardPrice.priceHistory, + // }; + // }); + // }; + + const emitUpdatedCards = (socket, updatedCards) => { + socket.emit('UPDATED_MONITORED_CARDS', updatedCards); }; + // Usage + // Now you can set the state with newCardPrices + + // const listOfMonitoredCards = useMemo(() => { + // if (!allCollections) return []; + + // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; + + // // Flatten all cards from all collections with additional collection ID + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); + + // // Reduce the cards to a map, merging cards with the same ID + // const mergedCardsMap = cardsWithCollectionId.reduce((acc, card) => { + // const updatedCardInfo = cardPrices.find((price) => price.id === card.id) || {}; + // const existingCard = acc.get(card.id) || {}; + + // const mergedCard = { + // ...existingCard, + // ...card, + // ...updatedCardInfo, + // latestPrice: updatedCardInfo.latestPrice || card.latestPrice || existingCard.latestPrice, + // lastSavedPrice: updatedCardInfo.lastSavedPrice || card.lastSavedPrice || existingCard.lastSavedPrice, + // price: updatedCardInfo.latestPrice?.num || card.price || existingCard.price, + // }; + + // acc.set(card.id, mergedCard); + // return acc; + // }, new Map()); + + // // Convert the map values to an array + // return Array.from(mergedCardsMap.values()); + // }, [allCollections, state.cardPrices]); + + // const listOfMonitoredCards = useMemo(() => { + // if (!allCollections) return []; + + // // Ensure cardPrices is an array + // const cardPrices = Array.isArray(state.cardPrices) ? state.cardPrices : []; + + // // Flatten all cards from all collections, including collection ID + // const cardsWithCollectionId = allCollections.flatMap((collection) => + // collection?.cards?.map((card) => ({ + // ...card, + // collectionId: collection._id, + // })) + // ); + + // // Create a unique set of card IDs + // const uniqueCardIds = new Set(cardsWithCollectionId.map((card) => card.id)); + + // // Map over unique card IDs to find corresponding card and update with new prices if available + // return cardsWithCollectionId.map((card) => { + // const updatedCardInfo = + // cardPrices.find((price) => price.id === card.id) || {}; + + // return { + // ...card, + // latestPrice: updatedCardInfo?.latestPrice || card?.latestPrice, + // lastSavedPrice: updatedCardInfo?.lastSavedPrice || card?.lastSavedPrice, + // price: updatedCardInfo?.latestPrice?.num || card.price, // Assuming you want to update the price field as well + // _id: updatedCardInfo?._id || card?._id, + // id: updatedCardInfo?.id || card?.id, + // collectionId: updatedCardInfo?.collectionId || card?.collectionId, + // tag: updatedCardInfo?.tag || card?.tag, + // name: updatedCardInfo?.name || card?.name, + // quantity: updatedCardInfo?.quantity || card?.quantity, + // priceHistory: updatedCardInfo?.priceHistory || card?.priceHistory, + // // __v: updatedCardInfo?.__v || originalCard.__v, + // // _id: card?._id, + // // id: card?.id, + // // collectionId: card?.collectionId, // Include collection ID in the returned object + // // tag: 'monitored', + // // name: card?.name, + // // quantity: card?.quantity, + // // price: card?.price, + // // latestPrice: { + // // num: updatedLatestPrice, + // // timestamp: card?.latestPrice?.timestamp + // // ? card?.latestPrice?.timestamp + // // : new Date().toISOString(), + // // }, + // // lastSavedPrice: { + // // num: updatedLastSavedPrice, + // // timestamp: card?.lastSavedPrice?.timestamp + // // ? card?.lastSavedPrice?.timestamp + // // : new Date().toISOString(), + // // }, + // // priceHistory: card?.priceHistory, + // // __v: card?.__v, + // }; + // }); + // }, [allCollections, state.cardPrices]); + + // ----------- SOCKET EVENT HANDLERS ----------- + + // const safeEmit = useCallback( + // (event, data) => { + // try { + // if (!validateData(data, event, 'safeEmit')) { + // throw new Error(`Invalid data emitted for event: ${event}`); + // } + // if (socket) { + // socket.emit(event, data); + // console.log(`[Info] Emitted event: ${event}`); + // } else { + // console.warn('Socket is not connected. Cannot emit event:', event); + // } + // } catch (error) { + // console.error(`[Error] Failed to emit event: ${event}`, error); + // setDataFunctions.error({ + // message: error.message, + // source: 'safeEmit', + // }); + // } + // }, + // [socket] + // ); + + // const safeOn = useCallback( + // (event, handler) => { + // const wrapper = (data) => { + // try { + // if (!validateData(data, event, handler.name)) { + // throw new Error(`Invalid data received for event: ${event}`); + // } // Add this line to validate the data received + // // console.log(`[Info] Handling event: ${event}`); + // handler(data); + // } catch (error) { + // console.error(`[Error] Failed to handle event: ${event}`, error); + // setDataFunctions.error({ message: error.message, source: event }); + // } + // }; + + // socket.on(event, wrapper); // Add this line to register the event listener + + // return () => { + // socket.off(event, wrapper); // Add this line to unregister the event listener when the component unmounts + // }; + // }, + // [socket] + // ); + + // const mergeUpdates = (currentArray, updates) => { + // const updatedArray = [...currentArray]; + // updates.forEach((update) => { + // const index = updatedArray.findIndex((item) => item.id === update.id); + // if (index !== -1) { + // updatedArray[index] = { ...updatedArray[index], ...update }; + // } else { + // updatedArray.push(update); + // } + // }); + // return updatedArray; + // }; + + // const handleStatusUpdateCharts = (newData) => { + // const { updates } = newData.data; + // console.log('[STATUS_UPDATE_CHARTS] Data:', updates); + // const updatedList = mergeUpdates(state.listOfSimulatedCards, updates); + // setDataFunctions.listOfSimulatedCards(updatedList); + // }; + const handleStatusUpdateCharts = async (newData) => { + console.log('[STATUS_UPDATE_CHARTS] Data:', newData); + console.log('Card prices retrieved:', newData); + // const processedPrices = processCardPrices(newData, selectedCollection); + console.log('Card prices updated:', newData); + + // Filter out cards which are not in the selected collection + // const filteredCards = newData.data.data.filter((card) => { + // const cardIds = selectedCollection?.cards?.map((card) => card.id); + // return cardIds?.includes(card.id); + // }); + + // Merge the selectedCollection cards with the filteredCards by adding the latestPrice, lastSavedPrice and priceHistory + // selectedCollection should receive: price: latestPrice.num, priceHistory: [...priceHistory, latestPrice.num], lastSavedPrice: latestPrice + // monitoredCards should then be updated with the new values of quantity from the card data in selectedCollection + // const updatedAllCardPrices = processedPrices.priceArray; + // const updatedTotalPrice = processedPrices.totalPrice; + // const filteredCards = processedPrices.filteredCards; + // console.log('********** [--------------] **********'); + // console.log('********** [FILTERED CARDS] **********', filteredCards); + + // console.log('********** [--------------] **********'); + // console.log('********** [UPDATED PRICES] **********', updatedAllCardPrices); + + // console.log('********** [--------------] **********'); + // console.log('********** [UPDATED TOTAL] **********', updatedTotalPrice); + + // console.log('********** [--------------] **********'); + + // Start with the current collection's state + let updatedCollection = { ...selectedCollection }; + + // setTotalPrice(updatedTotalPrice); + // setAllCardPrices(updatedAllCardPrices); + // console.log('Updated collection in combined:', updatedCollection); + // Iterate over the array of processed prices + // for (const card of filteredCards) { + // updatedCollection = await updateOneFromCollection(card); + // } + + // return updatedCollection; + // const updatedCollection = await updateCollection({ + // ...selectedCollection, + // cards: filteredCards, + // }); + + console.log('Updated collection in combined:', updatedCollection); + return updatedCollection; + }; const handleReceive = (message) => { console.log('Received message:', message); setDataFunctions.messageTest(message); @@ -399,32 +664,72 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.collectionData(collectionData); }; - const handleExistingChartData = (chartData) => { - console.log('Existing chart data:', chartData); - setDataFunctions.existingChartData(chartData); - }; + // const handleExistingChartData = (chartData) => { + // console.log('Existing chart data:', chartData); + // setDataFunctions.existingChartData(chartData); + // }; - const handleChartDatasetsUpdated = (chartUpdate) => { - console.log('Chart datasets updated:', chartUpdate); - setDataFunctions.currentChartData(chartUpdate); - }; + // const handleChartDatasetsUpdated = (chartUpdate) => { + // console.log('Chart datasets updated:', chartUpdate); + // setDataFunctions.currentChartData(chartUpdate); + // }; - const handleCardPricesUpdated = async (priceData) => { + const handleCardPricesUpdated = (priceData) => { console.log('Card prices retrieved:', priceData); - const processedPrices = processCardPrices(priceData); - console.log('Card prices updated:', processedPrices); + // Update listOfMonitoredCards based on the updated card prices + const currentListOfMonitoredCards = + generateListOfMonitoredCards(allCollections); + console.log( + `[currentListOfMonitoredCards: $${getNewTotalPrice( + currentListOfMonitoredCards + )}] | `, + currentListOfMonitoredCards + ); + const updatedCardPrices = priceData.data.data; + // updatedCardPrices.forEach( + // ( + // card // Update the price of each card in the listOfMonitoredCards + // ) => { + // card.price = currentListOfMonitoredCards.price || card.price; + // } + // ); + + setDataFunctions.cardPrices(updatedCardPrices); + // console.log( + // `[updatedCardPrices: $${getNewTotalPrice(updatedCardPrices)}] | `, + // updatedCardPrices + // ); + // console.log( + // `[state.cardPrices.data: $${getNewTotalPrice(state.cardPrices.data)}] | `, + // state.cardPrices.data + // ); + const updatedListOfMonitoredCards = updateCardPricesInList( + currentListOfMonitoredCards, + updatedCardPrices + ); + console.log( + `[updatedListOfMonitoredCards: $${getNewTotalPrice( + updatedListOfMonitoredCards + )}] | `, + updatedListOfMonitoredCards + ); - // Start with the current collection's state - let updatedCollection = { ...selectedCollection }; + // Now update the listOfMonitoredCards in your state + setDataFunctions.allCardPrices(updatedListOfMonitoredCards); + const updatedCollectionResult = updateCollection( + selectedCollection, + null, // since we're not updating a specific card + 'update', // Assuming 'update' is the operation when prices change + userId + ); - // Iterate over the array of processed prices - for (const card of priceData.data.data) { - // Update each card within the collection - updatedCollection = await updateOneFromCollection(card, 'update'); + if (updatedCollectionResult) { + // Do something with the updated collection + console.log('Updated Collection:', updatedCollectionResult); + // Update your state/context or perform additional actions } - - console.log('Updated collection in combined:', updatedCollection); - return updatedCollection; + // Additional processing if required + // ... }; const handleNoPricesChanged = () => { @@ -482,8 +787,8 @@ export const CombinedProvider = ({ children }) => { ['EMITTED_RESPONSES', handleEmittedResponses], ['RESPONSE_CRON_DATA', handleCronJobTracker], ['RESPONSE_EXISTING_COLLECTION_DATA', handleExistingCollectionData], - ['RESPONSE_EXISTING_CHART_DATA', handleExistingChartData], - ['CHART_DATASETS_UPDATED', handleChartDatasetsUpdated], + // ['RESPONSE_EXISTING_CHART_DATA', handleExistingChartData], + // ['CHART_DATASETS_UPDATED', handleChartDatasetsUpdated], ['SEND_PRICING_DATA_TO_CLIENT', handleCardPricesUpdated], ['NO_PRICE_CHANGES', handleNoPricesChanged], ['SEND_UPDATED_DATA_TO_CLIENT', handleNewCardDataObject], @@ -500,9 +805,11 @@ export const CombinedProvider = ({ children }) => { ]); eventHandlers.forEach((handler, event) => { + // validateData(event, 'event', 'useEffect'); socket.on(event, handler); }); + validateData(eventHandlers, 'eventHandlers', 'useEffect'); return () => { eventHandlers.forEach((_, event) => { socket.off(event); @@ -575,6 +882,10 @@ export const CombinedProvider = ({ children }) => { if (!listOfMonitoredCards) return console.error('Missing retrievedListOfMonitoredCards.'); + console.log( + 'SENDING CHECK AND UPDATE CARD PRICES', + listOfMonitoredCards + ); const selectedList = listOfMonitoredCards; socket.emit('REQUEST_CRON_UPDATED_CARDS_IN_COLLECTION', { userId, @@ -634,11 +945,11 @@ export const CombinedProvider = ({ children }) => { handleSocketInteraction.sendAction.message('Hello from client!'); // handleSocketInteraction.sendAction.updateCollection(); // handleSocketInteraction.sendAction.updateChart(); - handleSocketInteraction.sendAction.checkAndUpdateCardPrices( - userId, - listOfMonitoredCards - // retrieveListOfMonitoredCards() - ); + // handleSocketInteraction.sendAction.checkAndUpdateCardPrices( + // userId, + // listOfMonitoredCards + // // retrieveListOfMonitoredCards() + // ); } }, [userId, selectedCollection, socket]); useEffect(() => { @@ -646,11 +957,30 @@ export const CombinedProvider = ({ children }) => { setDataFunctions.collectionData(selectedCollection); }, [selectedCollection]); + // useEffect(() => { + // if (state.allCardPrices) { + // console.log('ALL PRICE DATA', state.allCardPrices); + // // const oldTotal = getNewTotalPrice(state.cardPrices); + // const oldTotal2 = getNewTotalPrice(listOfMonitoredCards); + + // console.log('OLD TOTAL', oldTotal2); + // if ( + // JSON.stringify(state.allCardPrices) !== JSON.stringify(state.cardPrices) + // ) { + // console.log('SETTING SELECTED COLLECTION'); + // const newTotal = getNewTotalPrice(state.allCardPrices); + // console.log('NEW TOTAL COMBINED', newTotal); + // setAllCollections(state.collectionData); + // } + // } + // }, [state.allCardPrices]); + useEffect(() => { if (allCollections) { // console.log('allCollections', allCollections); // console.log('listOfMonitoredCards', listOfMonitoredCards); + console.log('ALLL', allCollections); if ( JSON.stringify(allCollections) !== JSON.stringify(state.allCollectionData) @@ -668,104 +998,46 @@ export const CombinedProvider = ({ children }) => { // ----------- CONTEXT VALUE ----------- // useEffect(() => { - // if (listOfMonitoredCards.length) + // if (listOfMonitoredCards.length === 0) // console.log('listOfMonitoredCards', listOfMonitoredCards); - // handleSocketInteraction.sendAction.triggerCronJob( - // userId, - // listOfMonitoredCards - // ); - // }, [listOfMonitoredCards]); + // setDataFunctions.listOfMonitoredCards({ listOfMonitoredCards }); + // // handleSocketInteraction.sendAction.checkAndUpdateCardPrices( + // // userId, + // // listOfMonitoredCards + // // ); + // }, [listOfMonitoredCards, allCollections]); const value = useMemo( () => ({ ...state, ...setDataFunctions, listOfMonitoredCards, - // emittedResponses: state.emittedResponses, - // messageTest: state.messageTest, - toast, confirm, setLoader, setCronStatus, - handleCronRequest: handleSocketInteraction.sendAction.triggerCronJob, // Ensure it's provided here + handleCronRequest: handleSocketInteraction.sendAction.triggerCronJob, handleSend: handleSocketInteraction.sendAction.message, handleRequestCollectionData: handleSocketInteraction.requestData.collection, - handleRequestChartData: handleSocketInteraction.requestData.chart, // Assuming this is the correct mapping + // handleRequestChartData: handleSocketInteraction.requestData.chart, handleSendAllCardsInCollections: - handleSocketInteraction.sendAction.checkAndUpdateCardPrices, // Ensure it's provided here - handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, // Ensure it's provided here - // handleRetreiveListOfMonitoredCards: retrieveListOfMonitoredCards, - - // MAIN LOGGER + handleSocketInteraction.sendAction.checkAndUpdateCardPrices, + handleRequestCronStop: handleSocketInteraction.sendAction.stopCronJob, handlePricesActivateCron: handleSocketInteraction.sendAction.checkPriceUpdates, handleSocketInteraction, - setDataFunctions, socket, isDelaying: state.isDelaying, isCronJobTriggered: state.isCronJobTriggered, - // setDataFunctions, }), [state, socket] ); + // Log combined context value for debugging useEffect(() => { console.log('COMBINED CONTEXT VALUE:', state); - }, [ - state.allData, - state.data, - state.messageTest, - state.existingChartData, - state.collectionData, - state.dcurrentChartData, - state.cronData, - state.finalUpdateData, - state.cardPrices, - state.eventsTriggered, - state.cardsWithChangedPrice, - state.previousDayTotalPrice, - state.dailyPriceChange, - state.priceDifference, - state.allCardPrices, - state.handleCardPricesUpdated, - state.retrievedListOfMonitoredCards, - ]); - - const dataValues = { - state: state, - allData: value.allData, - eventsTriggered: value.eventsTriggered, - data: value.data, - messageTest: value.messageTest, - finalUpdateData: value.finalUpdateData, - chartData: value.chartData, - emittedResponses: value.emittedResponses, - currentChartData: value.currentChartData, - existingChartData: value.existingChartData, - collectionData: value.collectionData, - allCollectionData: value.allCollectionData, - allCollectionsUpdated: value.allCollectionsUpdated, - cronData: value.cronData, - cardPrices: value.cardPrices, - retrievedListOfMonitoredCards: value.retrievedListOfMonitoredCards, - listOfMonitoredCards: value.listOfMonitoredCards, - error: value.error, - }; - - // useEffect(() => { - // console.log('COMBINED CONTEXT VALUE:', dataValues); - // }, [ - // dataValues.cronData, - // dataValues.chartData, - // dataValues.collectionData, - // dataValues.messageTest, - // dataValues.emittedResponses, - // dataValues.retrievedListOfMonitoredCards, - // dataValues.emittedResponses, - // dataValues.eventsTriggered, - // ]); + }, [state]); return ( diff --git a/src/index.js b/src/index.js index 435859a..6d584fe 100644 --- a/src/index.js +++ b/src/index.js @@ -25,13 +25,13 @@ import { createTheme } from '@mui/material'; const root = document.getElementById('root'); function Main() { - // const { theme } = useMode(); - const darkTheme = createTheme({ - palette: { - mode: 'dark', - }, - }); - const theme = darkTheme; + const { theme } = useMode(); + // const darkTheme = createTheme({ + // palette: { + // mode: 'dark', + // }, + // }); + // const theme = darkTheme; return ( diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index 4f22110..0e641f2 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -106,13 +106,13 @@ const ProfilePage = () => { } }; - const handleTriggerCronJob = () => { - if (userId && listOfMonitoredCards) { - handleSendAllCardsInCollections(userId, listOfMonitoredCards); - console.log('SENDING ALL CARDS IN COLLECTIONS'); - openSnackbar('Triggered the cron job.'); - } - }; + // const handleTriggerCronJob = () => { + // if (userId && listOfMonitoredCards) { + // handleSendAllCardsInCollections(userId, listOfMonitoredCards); + // console.log('SENDING ALL CARDS IN COLLECTIONS'); + // openSnackbar('Triggered the cron job.'); + // } + // }; const handleStopCronJob = () => { if (userId) { @@ -160,13 +160,13 @@ const ProfilePage = () => { Request Chart Data - Trigger Cron Job - + */} { @@ -30,7 +31,8 @@ export const themeSettings = (mode) => { light: colors.grey[100], }, background: { - default: mode === 'dark' ? colors.primary[500] : '#fcfcfc', + default: mode === 'dark' ? '#121212' : '#ffffff', + paper: mode === 'dark' ? colors.grey[300] : '#ffffff', }, error: { main: colors.redAccent[500],