From db0d493944a27041ae99da4f37543b9d5f0f7aac Mon Sep 17 00:00:00 2001 From: Francisco Pessano Date: Sat, 10 Jun 2023 21:17:16 -0300 Subject: [PATCH] feat: migrated from chart.js to google-charts --- .babelrc | 4 ++ .eslintrc.cjs | 24 ++++++++++ .gitignore | 3 +- .nvmrc | 1 + components/PieCircle.tsx | 73 ++++++++++++++++++++++------- lib/storage.ts | 8 +++- lib/theme.ts | 24 ++++++++++ lib/types.d.ts | 21 ++++++++- package.json | 37 ++++++++++++++- pages/index.css | 4 ++ pages/index.tsx | 71 ++++++++++++++++++++++------ screens/Header/Header.tsx | 12 +++-- screens/SpendScreen/SpendScreen.tsx | 25 ++++++++-- 13 files changed, 265 insertions(+), 42 deletions(-) create mode 100644 .babelrc create mode 100644 .nvmrc create mode 100644 lib/theme.ts diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..854cb73 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["next/babel"], + "plugins": [["styled-components", { "ssr": true }]] +} diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bef0c22..bc85252 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -13,7 +13,31 @@ module.exports = { plugins: ['react', 'prettier', 'sort-keys-fix', 'better-styled-components'], rules: { '@typescript-eslint/strict-boolean-expressions': 'off', + 'arrow-body-style': ['error', 'as-needed'], 'better-styled-components/sort-declarations-alphabetically': 2, + 'import/order': [ + 'error', + { + groups: [ + 'builtin', + 'external', + 'internal', + 'unknown', + 'parent', + 'sibling', + 'index', + 'object', + 'type', + ], + pathGroups: [ + { + group: 'external', + pattern: '~/**', + position: 'after', + }, + ], + }, + ], 'prettier/prettier': 'error', 'sort-keys-fix/sort-keys-fix': 'error', }, diff --git a/.gitignore b/.gitignore index 03cff09..583d0c3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ package-lock.json yarn-error.log yarn.lock .next -pages/normalize.css \ No newline at end of file +pages/normalize.css +pnpm-lock.yaml \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..32b6e49 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +node \ No newline at end of file diff --git a/components/PieCircle.tsx b/components/PieCircle.tsx index 8e531b4..dd069c3 100644 --- a/components/PieCircle.tsx +++ b/components/PieCircle.tsx @@ -1,22 +1,61 @@ import React from 'react'; -import { Chart as ChartJS, ArcElement, Tooltip, Legend } from 'chart.js'; -import { Pie } from 'react-chartjs-2'; +import Chart from 'react-google-charts'; +import styled, { useTheme } from 'styled-components'; +import { type Theme } from '@/lib/theme'; +import { type PieCircleData } from '@/lib/types'; -export const PieCircle = (): JSX.Element => { - ChartJS.register(ArcElement, Tooltip, Legend); +export const PieCircle = (props: { pieCircleData: PieCircleData }): JSX.Element => { + const { pieCircleData } = props; + const theme = useTheme() as Theme; + const { colors } = theme; + const [data, legendData, chartColors] = [ + pieCircleData.map(([[label, value]]) => [label, parseFloat(value.toFixed(2))]), + pieCircleData.map(([, item]) => item), + pieCircleData.map(([, { backgroundColor }]) => backgroundColor), + ]; return ( - + + +
+ {legendData.map(({ backgroundColor, label }) => ( + <> +
+ + {label} + + + ))} +
+
); }; + +const PieCircleContainer = styled.div` + height: 100%; + width: 100%; +`; + +const StyledChart = styled(Chart)` + background: 'none'; +`; diff --git a/lib/storage.ts b/lib/storage.ts index 90339dd..ab4fd66 100644 --- a/lib/storage.ts +++ b/lib/storage.ts @@ -1,16 +1,22 @@ /* eslint-disable @typescript-eslint/ban-types */ import { create } from 'zustand'; -import { type Tab } from '@/lib/types'; +import { type UserSpendData, type Tab } from '@/lib/types'; import { SPEND_SCREEN_ID, SPEND_SCREEN_NAME } from '@/lib/constants'; interface appStore { tab: Tab; setTab: (props: Tab) => void; + setUserSpendData: (props: UserSpendData[]) => void; + userSpendData: UserSpendData[]; } export const useAppStore = create((set) => ({ setTab: (props: Tab) => { set(() => ({ tab: props })); }, + setUserSpendData: (props: UserSpendData[]) => { + set(() => ({ userSpendData: props })); + }, tab: { id: SPEND_SCREEN_ID, title: SPEND_SCREEN_NAME }, + userSpendData: [], })); diff --git a/lib/theme.ts b/lib/theme.ts new file mode 100644 index 0000000..9a3f753 --- /dev/null +++ b/lib/theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable sort-keys-fix/sort-keys-fix */ +export interface Theme { + colors: { + primary: string; + secondary: string; + complementary: string; + textColor: { + primary: string; + }; + }; +} + +const theme: Theme = { + colors: { + primary: '#635985', + secondary: '#443C68', + complementary: '#393053', + textColor: { + primary: '#ddd', + }, + }, +}; + +export default theme; diff --git a/lib/types.d.ts b/lib/types.d.ts index 742f6b1..7a9d559 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -1,4 +1,23 @@ export interface Tab { - title: string; id: string; + title: string; } + +interface Currency { + id: string; + label: string; +} + +interface Category { + label: string; + backgroundColor: string; +} + +export interface UserSpendData { + category: Category; + currency: Currency; + date: Date; + value: number; +} + +export type PieCircleData = Array<[[string, number], { backgroundColor: string; label: string }]>; diff --git a/package.json b/package.json index f5673d4..8813058 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,11 @@ "husky-prepare": "husky install" }, "dependencies": { - "chart.js": "^4.2.1", "next": "^13.2.4", "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", + "react-google-charts": "^4.0.0", "styled-components": "^5.3.9", "zustand": "^4.3.7" }, @@ -31,6 +31,7 @@ "@types/styled-components": "^5.1.26", "@typescript-eslint/eslint-plugin": "^5.43.0", "@vitejs/plugin-react": "^3.1.0", + "babel-plugin-styled-components": "^2.0.7", "eslint": "^8.0.1", "eslint-config-prettier": "^8.8.0", "eslint-config-standard-with-typescript": "^34.0.1", @@ -51,10 +52,42 @@ "stylelint-order": "^6.0.3", "stylelint-prettier": "^3.0.0", "stylelint-processor-styled-components": "^1.10.0", - "typescript": "*", + "typescript": "^5.0.4", "vite": "^4.2.0" }, "resolutions": { "styled-components": "^5" + }, + "babel": { + "env": { + "development": { + "presets": [ + "next/babel" + ], + "plugins": [ + [ + "styled-components", + { + "ssr": true, + "displayName": true + } + ] + ] + }, + "production": { + "presets": [ + "next/babel" + ], + "plugins": [ + [ + "styled-components", + { + "ssr": true, + "displayName": false + } + ] + ] + } + } } } diff --git a/pages/index.css b/pages/index.css index ffa936e..3bddf56 100644 --- a/pages/index.css +++ b/pages/index.css @@ -26,3 +26,7 @@ a:hover { margin: 0; padding: 0; } + +#__next, body, html { + height: 100%; +} \ No newline at end of file diff --git a/pages/index.tsx b/pages/index.tsx index 77ae094..519fda9 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,20 +1,20 @@ import React from 'react'; +import Head from 'next/head'; +import styled, { ThemeProvider } from 'styled-components'; import { Header, SpendScreen } from '@/screens'; import { type Tab } from '@/lib/types'; import { APP_NAME, SPEND_SCREEN_ID } from '@/lib/constants'; -import Head from 'next/head'; import { useAppStore } from '@/lib/storage'; +import theme from '@/lib/theme'; -const HeadIndex = (): JSX.Element => { - return ( - <> - - {APP_NAME} - - - - ); -}; +const HeadIndex = (): JSX.Element => ( + <> + + {APP_NAME} + + + +); const appRender = ({ tab }: { tab: Tab }): JSX.Element => { switch (tab.id) { @@ -27,13 +27,56 @@ const appRender = ({ tab }: { tab: Tab }): JSX.Element => { function App(): JSX.Element { const tab = useAppStore((state) => state.tab); + const setUserSpendData = useAppStore((state) => state.setUserSpendData); + setUserSpendData([ + { + category: { backgroundColor: 'rgb(99, 128, 255)', label: 'invest' }, + currency: { + id: 'usd', + label: 'usd', + }, + date: new Date(), + value: 124, + }, + { + category: { backgroundColor: 'rgb(99, 128, 255)', label: 'invest' }, + currency: { + id: 'usd', + label: 'usd', + }, + date: new Date(), + value: 124.1, + }, + { + category: { backgroundColor: 'rgb(54, 162, 235)', label: 'school' }, + currency: { + id: 'usd', + label: 'usd', + }, + date: new Date(), + value: 124.43335, + }, + { + category: { backgroundColor: 'rgb(145, 86, 255)', label: 'party' }, + currency: { + id: 'usd', + label: 'usd', + }, + date: new Date(), + value: 1242, + }, + ]); return ( - <> +
- {appRender({ tab })} - + {appRender({ tab })} + ); } +const Body = styled.div` + height: 100%; +`; + export default App; diff --git a/screens/Header/Header.tsx b/screens/Header/Header.tsx index e1d8f22..3b0c4b4 100644 --- a/screens/Header/Header.tsx +++ b/screens/Header/Header.tsx @@ -2,6 +2,7 @@ import React from 'react'; import styled from 'styled-components'; import { useAppStore } from '@/lib/storage'; +import { type Theme } from '@/lib/theme'; import { tabs } from './data'; export const Header = (): JSX.Element => { @@ -17,7 +18,7 @@ export const Header = (): JSX.Element => { setTab(tabData); }} > -

{tabData.title}

+ {tabData.title} ))} @@ -25,14 +26,15 @@ export const Header = (): JSX.Element => { }; const TabsContainer = styled.div` - background: #635985; + background: ${({ theme }) => theme.colors.secondary}; display: flex; `; const StyledTab = styled.div<{ active: boolean; }>` - background: ${({ active }) => (active ? '#443C68' : '#635985')}; + background: ${({ active, theme }: { active: boolean; theme: Theme }) => + active ? theme.colors.complementary : theme.colors.secondary}; padding: 12px 0px; text-align: center; transition: 0.2s ease-in-out all; @@ -43,3 +45,7 @@ const StyledTab = styled.div<{ transition: 0.2s ease-in-out all; } `; + +const TabText = styled.h3` + color: ${({ theme }: { theme: Theme }) => theme.colors.textColor.primary}; +`; diff --git a/screens/SpendScreen/SpendScreen.tsx b/screens/SpendScreen/SpendScreen.tsx index d6307d3..297ec0e 100644 --- a/screens/SpendScreen/SpendScreen.tsx +++ b/screens/SpendScreen/SpendScreen.tsx @@ -1,10 +1,29 @@ import React from 'react'; +import styled from 'styled-components'; import { PieCircle } from '@/components'; +import { type Theme } from '@/lib/theme'; +import { useAppStore } from '@/lib/storage'; +import { type PieCircleData } from '@/lib/types'; export const SpendScreen = (): JSX.Element => { + const userSpendData = useAppStore((state) => state.userSpendData); + const reducedUserData = userSpendData.reduce( + (acc, value) => + acc.set(value.category.label, [...(acc.get(value.category.label) || []), value]), + new Map(), + ); + const combinedUserData: PieCircleData = [...reducedUserData.entries()].map(([key, values]) => [ + [key, values.reduce((acc: number, { value }: { value: number }) => acc + value, 0)], + { backgroundColor: values[0].category.backgroundColor, label: values[0].category.label }, + ]); return ( - <> - - + + + ); }; + +const SpendScreenContainer = styled.div` + background-color: ${({ theme }: { theme: Theme }) => theme.colors.primary}; + height: 100%; +`;