From b6bcb5261e8e54ac60d5e3e336429db5ebc5a367 Mon Sep 17 00:00:00 2001 From: Sawa <34698182+sawaYch@users.noreply.github.com> Date: Fri, 15 Mar 2024 05:24:21 +0800 Subject: [PATCH] feat(ui): use recharts achieve better responsiveness --- components/poll-process-result-section.tsx | 211 ++++++++++++------ components/prepare-section.tsx | 11 +- components/suppress-default-props-warning.tsx | 29 +++ hooks/use-chart-config.ts | 111 +-------- hooks/use-fetch-livechat.ts | 10 +- package-lock.json | 27 --- package.json | 4 +- 7 files changed, 187 insertions(+), 216 deletions(-) create mode 100644 components/suppress-default-props-warning.tsx diff --git a/components/poll-process-result-section.tsx b/components/poll-process-result-section.tsx index 80252f7..6789509 100644 --- a/components/poll-process-result-section.tsx +++ b/components/poll-process-result-section.tsx @@ -2,37 +2,26 @@ import NewPollConfirmDialog from '@/components/new-poll-confirm-dialog'; import Spinner from '@/components/spinner'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; -import { - updateChartResultParam, - useChartConfig, -} from '@/hooks/use-chart-config'; +import { useChartConfig } from '@/hooks/use-chart-config'; import { useFetchLiveChat } from '@/hooks/use-fetch-livechat'; import { cn } from '@/lib/utils'; import { usePollAppStore } from '@/stores/store'; -import { - BarElement, - CategoryScale, - Chart as ChartJS, - Legend, - LinearScale, - Title, - Tooltip, -} from 'chart.js'; import { StopCircleIcon } from 'lucide-react'; -import { useCallback, useEffect, useRef } from 'react'; -import { Bar } from 'react-chartjs-2'; +import { useCallback, useEffect, useMemo, useRef } from 'react'; import { BrowserView, MobileView, isMobile } from 'react-device-detect'; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; import MotionContainer from './motion-container'; import PollSummarySubCard from './poll-summary-subcard'; - -ChartJS.register( - CategoryScale, - LinearScale, - BarElement, - Title, - Tooltip, - Legend -); +import SuppressDefaultPropsWarning from './suppress-default-props-warning'; const PollProcessResultSection = () => { const { @@ -40,46 +29,72 @@ const PollProcessResultSection = () => { changePollAppState, newPollReset, pollResultSummary, - setPollResultSummary, + numOfOptions, } = usePollAppStore(); - const { chartOptions, chartInitData, sortChartResult } = useChartConfig(); - const barChartRef = useRef>(null); - - const updateChartInPolling = useCallback( - (data: number[]) => { - if (barChartRef.current) { - barChartRef.current.data.datasets[0].data = data; - barChartRef.current.update(); - setPollResultSummary(data); - } - }, - [setPollResultSummary] - ); + const { chartInitData } = useChartConfig(); - const updateChartPollResult = useCallback((param: updateChartResultParam) => { - if (barChartRef.current) { - barChartRef.current.data.datasets[0].data = param.newArrayData; - barChartRef.current.data.datasets[0].backgroundColor = param.newBarColor; - barChartRef.current.data.datasets[0].borderColor = param.newBorderColor; - barChartRef.current.data.labels = param.newArrayLabel; - barChartRef.current.update(); - } - }, []); - - useFetchLiveChat({ updateChart: updateChartInPolling }); + useFetchLiveChat(); const handleProceedNewPoll = useCallback(() => { newPollReset(); + const divElement = document.getElementById('prepare-section-card'); + if (divElement) { + divElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); + } }, [newPollReset]); const cardRef = useRef(null); useEffect(() => { if (cardRef.current) { - cardRef.current.scrollIntoView({ behavior: 'smooth', block: 'end' }); + cardRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, [cardRef]); + const enableRankedChartResult = useMemo(() => { + return pollAppState === 'stop'; + }, [pollAppState]); + + const barChartData = useMemo(() => { + if (pollResultSummary.length === 0) { + return new Array(numOfOptions).fill(0).map((value, index) => { + return { + name: String(index + 1), + color: chartInitData.datasets[0].backgroundColor[index], + value, + }; + }); + } + + if (enableRankedChartResult) { + const rankedResult = pollResultSummary + .map((value, index) => { + return { + name: String(index + 1), + color: chartInitData.datasets[0].backgroundColor[index], + value, + }; + }) + .sort((a, b) => (a.value === b.value ? 0 : a.value > b.value ? -1 : 1)); + + rankedResult[0].name = `👑${rankedResult[0].name}`; + return rankedResult; + } + + return pollResultSummary.map((value, index) => { + return { + name: String(index + 1), + color: chartInitData.datasets[0].backgroundColor[index], + value, + }; + }); + }, [ + chartInitData.datasets, + enableRankedChartResult, + numOfOptions, + pollResultSummary, + ]); + return ( { 'h-full': isMobile, })} > - + {pollAppState === 'stop' ? ( @@ -105,30 +120,87 @@ const PollProcessResultSection = () => { )} +
-
- -
+ + + + + + value.toLocaleString()} + /> + + + {barChartData.map((d, _idx) => { + return ; + })} + + +
- {/*
- -
FIXME: responsive here sucks!!!*/} + + + + + + value.toLocaleString()} + /> + + + {barChartData.map((d, _idx) => { + return ; + })} + + +
@@ -141,7 +213,6 @@ const PollProcessResultSection = () => { className='mt-8 flex w-32 self-end' onClick={() => { changePollAppState('stop'); - sortChartResult(updateChartPollResult); }} > Stop diff --git a/components/prepare-section.tsx b/components/prepare-section.tsx index 8095e77..d44a9b8 100644 --- a/components/prepare-section.tsx +++ b/components/prepare-section.tsx @@ -33,7 +33,7 @@ const PrepareSection = () => { return ( - + 1️⃣ Prepare @@ -113,6 +113,15 @@ const PrepareSection = () => { changePollAppState('start'); setPollResultSummary(new Array(numOfOptions).fill(0)); setPollStartDateTime(dayjs()); + const divElement = document.getElementById( + 'process-section-card' + ); + if (divElement) { + divElement.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + } }} > Start Poll diff --git a/components/suppress-default-props-warning.tsx b/components/suppress-default-props-warning.tsx new file mode 100644 index 0000000..1a90b7e --- /dev/null +++ b/components/suppress-default-props-warning.tsx @@ -0,0 +1,29 @@ +'use client'; + +import React, { useEffect } from 'react'; + +/** + * FIXME: This component is workaround for recharts warning + * see: https://github.com/recharts/recharts/issues/3615 + */ +const SuppressDefaultPropsWarning: React.FC = () => { + useEffect(() => { + const originalConsoleError = console.error; + + console.error = (...args: any[]) => { + if (typeof args[0] === 'string' && /defaultProps/.test(args[0])) { + return; + } + + originalConsoleError(...args); + }; + + return () => { + console.error = originalConsoleError; + }; + }, []); + + return null; +}; + +export default SuppressDefaultPropsWarning; diff --git a/hooks/use-chart-config.ts b/hooks/use-chart-config.ts index 9bce2ba..294222f 100644 --- a/hooks/use-chart-config.ts +++ b/hooks/use-chart-config.ts @@ -1,6 +1,6 @@ import { randomRGBAColor } from '@/lib/random-rgba-color'; import { usePollAppStore } from '@/stores/store'; -import { useCallback, useMemo } from 'react'; +import { useMemo } from 'react'; export type updateChartResultParam = { newArrayData: number[]; @@ -10,59 +10,7 @@ export type updateChartResultParam = { }; export const useChartConfig = () => { - const { numOfOptions, pollData, setPollResultSummary } = usePollAppStore(); - - const chartOptions = useMemo(() => { - return { - indexAxis: 'y' as const, - elements: { - bar: { - borderWidth: 2, - }, - }, - responsive: true, - plugins: { - legend: { - display: false, - }, - title: { - display: true, - text: 'Realtime poll response graph', - }, - }, - scales: { - y: { - // title: { - // display: true, - // text: 'Option', - // color: '#dddddd', - // }, - ticks: { - color: '#dddddd', - }, - grid: { - color: '#81112a', - lineWidth: 0.5, - }, - }, - x: { - // title: { - // display: true, - // color: '#dddddd', - // text: 'Count', - // }, - ticks: { - color: '#dddddd', - stepSize: 1, - }, - grid: { - color: '#cd1b42', - lineWidth: 1, - }, - }, - }, - }; - }, []); + const { numOfOptions } = usePollAppStore(); const chartColor = useMemo(() => { return randomRGBAColor(numOfOptions); @@ -87,58 +35,5 @@ export const useChartConfig = () => { }; }, [chartColor.bar, chartColor.border, numOfOptions]); - const sortChartResult = useCallback( - (updateChartResult: (param: updateChartResultParam) => void) => { - const data = new Array(numOfOptions).fill(0); - - Object.values(pollData).forEach((v) => { - if (v > 0 && v <= numOfOptions) { - data[v - 1]++; - } - }); - - const arrayOfObj = Array.from(Array(numOfOptions).keys()) - .map((i) => i + 1) - .map((value, index) => { - return { - label: value, - data: data[index] || 0, - borderColor: chartColor.border[index], - backgroundColor: chartColor.bar[index], - }; - }); - - const pollSummary = arrayOfObj.map((it) => it.data); - setPollResultSummary(pollSummary); - const sortedArrayOfObj = arrayOfObj.sort((a, b) => - a.data === b.data ? 0 : a.data > b.data ? -1 : 1 - ); - - const newArrayLabel: string[] = []; - const newArrayData: number[] = []; - const newBarColor: string[] = []; - const newBorderColor: string[] = []; - sortedArrayOfObj.forEach(function (d, index) { - if (index === 0) { - const highestVoteLabel = `👑${d.label}`; - newArrayLabel.push(highestVoteLabel); - } else { - newArrayLabel.push(`${d.label}`); - } - newArrayData.push(d.data); - newBarColor.push(d.backgroundColor); - newBorderColor.push(d.borderColor); - }); - - updateChartResult({ - newArrayData, - newBarColor, - newBorderColor, - newArrayLabel, - }); - }, - [chartColor, numOfOptions, pollData, setPollResultSummary] - ); - - return { chartOptions, chartInitData, sortChartResult }; + return { chartInitData }; }; diff --git a/hooks/use-fetch-livechat.ts b/hooks/use-fetch-livechat.ts index 3233292..f6491d1 100644 --- a/hooks/use-fetch-livechat.ts +++ b/hooks/use-fetch-livechat.ts @@ -6,11 +6,7 @@ import dayjs from 'dayjs'; import { useCallback, useEffect, useRef } from 'react'; import { useLiveChat } from './use-livechat'; -interface useFetchLiveChatProps { - updateChart: (data: number[]) => void; -} - -export const useFetchLiveChat = ({ updateChart }: useFetchLiveChatProps) => { +export const useFetchLiveChat = () => { const { setIsLoading, pollData, @@ -108,8 +104,9 @@ export const useFetchLiveChat = ({ updateChart }: useFetchLiveChatProps) => { } }); - updateChart(data); + // update poll result summary (choice & count) setPollResultSummary(data); + // update poll Data based on last existing poll data record (existedPollData[uid] = choice) setPollData(existedPollData); setTimeout(async () => { @@ -127,7 +124,6 @@ export const useFetchLiveChat = ({ updateChart }: useFetchLiveChatProps) => { setPollData, setPollResultSummary, toast, - updateChart, ] ); diff --git a/package-lock.json b/package-lock.json index 97774ef..fcf3351 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", - "chart.js": "^4.4.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dayjs": "^1.11.10", @@ -29,7 +28,6 @@ "next-themes": "^0.2.1", "randomcolor": "^0.6.2", "react": "^18", - "react-chartjs-2": "^5.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18", "recharts": "^2.12.2", @@ -307,11 +305,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@kurkle/color": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", - "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" - }, "node_modules/@next/env": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", @@ -2053,17 +2046,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chart.js": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", - "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", - "dependencies": { - "@kurkle/color": "^0.3.0" - }, - "engines": { - "pnpm": ">=8" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4972,15 +4954,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-chartjs-2": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", - "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", - "peerDependencies": { - "chart.js": "^4.1.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-device-detect": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-device-detect/-/react-device-detect-2.2.3.tgz", diff --git a/package.json b/package.json index 3c00c32..6988672 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mya-poll", - "version": "0.2.0", + "version": "0.2.1", "private": true, "scripts": { "dev": "next dev", @@ -22,7 +22,6 @@ "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-toggle": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", - "chart.js": "^4.4.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "dayjs": "^1.11.10", @@ -32,7 +31,6 @@ "next-themes": "^0.2.1", "randomcolor": "^0.6.2", "react": "^18", - "react-chartjs-2": "^5.2.0", "react-device-detect": "^2.2.3", "react-dom": "^18", "recharts": "^2.12.2",