diff --git a/package-lock.json b/package-lock.json index 7f57621..30389c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,12 +15,15 @@ "@react-email/components": "0.0.14", "axios": "1.6.7", "bcryptjs": "2.4.3", + "chart.js": "^4.4.1", "classnames": "2.5.1", "jose": "5.2.1", "jsonwebtoken": "9.0.2", "next": "14.1.0", "react": "18.2.0", + "react-chartjs-2": "^5.2.0", "react-code-input": "3.10.1", + "react-credit-cards-2": "^1.0.2", "react-dom": "18.2.0", "react-hook-form": "7.50.1", "react-simple-typewriter": "5.0.1", @@ -1897,6 +1900,11 @@ "@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", @@ -4099,6 +4107,17 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.1.tgz", + "integrity": "sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=7" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -4304,6 +4323,11 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/credit-card-type": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-9.1.0.tgz", + "integrity": "sha512-CpNFuLxiPFxuZqhSKml3M+t0K/484pMAnfYWH14JoD7OZMnmC0Lmo+P7JX9SobqFpRoo7ifA18kOHdxJywYPEA==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5915,7 +5939,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", - "dev": true, "dependencies": { "define-properties": "^1.1.3" }, @@ -8038,6 +8061,14 @@ "node": ">=10" } }, + "node_modules/luhn": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/luhn/-/luhn-2.4.1.tgz", + "integrity": "sha512-p1eEWSb2RJAfv+BzrPEUqbUXV1H3ylHEAOk9yDM1L12ojD6OQQGrE29DqKLa0XYluJTIwXIOFmyzVOme3QSyww==", + "engines": { + "node": ">=10" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -8679,6 +8710,15 @@ "node": ">=8" } }, + "node_modules/payment": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/payment/-/payment-2.4.6.tgz", + "integrity": "sha512-QSCAa1yQSkqbe4Ghac3sSA5SQ+Cxc3e4xwCxxun5NT6hUSWsNXXlN8KCCY0kAFFXBP9C7DrfyXP4REB7nPJa8g==", + "dependencies": { + "globalthis": "^1.0.2", + "qj": "~2.0.0" + } + }, "node_modules/peberminta": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", @@ -9073,6 +9113,11 @@ } ] }, + "node_modules/qj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/qj/-/qj-2.0.0.tgz", + "integrity": "sha512-8466vlnAF/piI42tzMBUfhaAWn2yBNPOLSSbA2YBlEh+S8CxBXbAO1AwuDReGKYX6LlsK19wBL9cpXZGlgsXxA==" + }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -9110,6 +9155,15 @@ "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-code-input": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/react-code-input/-/react-code-input-3.10.1.tgz", @@ -9160,6 +9214,19 @@ "object-assign": "^4.1.1" } }, + "node_modules/react-credit-cards-2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-credit-cards-2/-/react-credit-cards-2-1.0.2.tgz", + "integrity": "sha512-XvDa7x+rzdtrHIglARMhiQpuWIqMP1bSN8gajBGD93Zn4Y3DdhPKEPKafChTAOAfhMRZpFZ23DUXNQWJAIUDFA==", + "dependencies": { + "credit-card-type": "^9.1.0", + "luhn": "^2.4.1", + "payment": "^2.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dom": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", diff --git a/package.json b/package.json index 1c94cbf..e86aed5 100644 --- a/package.json +++ b/package.json @@ -19,12 +19,15 @@ "@react-email/components": "0.0.14", "axios": "1.6.7", "bcryptjs": "2.4.3", + "chart.js": "^4.4.1", "classnames": "2.5.1", "jose": "5.2.1", "jsonwebtoken": "9.0.2", "next": "14.1.0", "react": "18.2.0", + "react-chartjs-2": "^5.2.0", "react-code-input": "3.10.1", + "react-credit-cards-2": "^1.0.2", "react-dom": "18.2.0", "react-hook-form": "7.50.1", "react-simple-typewriter": "5.0.1", diff --git a/src/app/dashboard/(control-panel)/money/index.tsx b/src/app/dashboard/(control-panel)/money/index.tsx index 62ffb11..c563a58 100644 --- a/src/app/dashboard/(control-panel)/money/index.tsx +++ b/src/app/dashboard/(control-panel)/money/index.tsx @@ -1,10 +1,13 @@ -import React from 'react' -import Text from '@/components/atoms/text/Text' +'use client' + +import React, { useContext } from 'react' +import MoneyTemplate from '@/components/templates/dashboard/money' +import { userContext } from '@/context/userContext' export default function Money (): JSX.Element { + const user = useContext(userContext) + return ( - - Home - + ) } diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index c6e83c9..41caaf2 100644 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -1,6 +1,7 @@ import React from 'react' import type { Metadata } from 'next' import DashboardContainer from '@/components/pages/layouts/dashboard-layout' +import { UserProvider } from '@/context/userContext' export const metadata: Metadata = { title: 'Panel de control | Golden Duck' @@ -11,5 +12,9 @@ export default function DashboardLayout ({ }: { children: React.ReactNode }): JSX.Element { - return {children} + return ( + + {children} + + ) } diff --git a/src/components/molecules/buttons/profile-button/index.tsx b/src/components/molecules/buttons/profile-button/index.tsx index 4e2bfa2..93b06e5 100644 --- a/src/components/molecules/buttons/profile-button/index.tsx +++ b/src/components/molecules/buttons/profile-button/index.tsx @@ -1,6 +1,5 @@ import React from 'react' import Text from '@/components/atoms/text/Text' -import Image from 'next/image' import style from './styles.module.scss' interface Props { @@ -14,7 +13,7 @@ export default function ProfileButton ({ }: Props): JSX.Element { return (
- Profile Picture + Profile Picture Hola,{' '} diff --git a/src/components/molecules/cards/stats-card/index.tsx b/src/components/molecules/cards/stats-card/index.tsx new file mode 100644 index 0000000..d6a3252 --- /dev/null +++ b/src/components/molecules/cards/stats-card/index.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import style from './styles.module.scss' +import classNames from 'classnames' + +interface Props { + title: string + value: string + progress: number + classname?: string + icon?: React.ReactNode + iconBGColor?: string +} + +export default function InfoCard ({ title, value, progress, classname, icon, iconBGColor }: Props): JSX.Element { + const classes = classNames(style.InfoCard, classname) + const spanClasses = classNames({ [style.positive]: progress > 0 }, { [style.negative]: progress < 0 }) + + return ( +
+
+

{title}

+ {value} +

{Math.abs(progress)} desde ayer

+
+ { + icon !== undefined && ( +
+ {icon} +
+ ) + } +
+ ) +} diff --git a/src/components/molecules/cards/stats-card/styles.module.scss b/src/components/molecules/cards/stats-card/styles.module.scss new file mode 100644 index 0000000..c6b01e8 --- /dev/null +++ b/src/components/molecules/cards/stats-card/styles.module.scss @@ -0,0 +1,60 @@ +.InfoCard{ + display: flex; + max-height: 10rem; + padding: 1rem 1.3rem; + background-color: oklch(100% 0 0); + box-shadow: 0 0 2rem 0 rgba(136,152,170,.15); + border-radius: 1rem; + gap: 2rem; + article{ + flex: 2; + h2{ + text-transform: capitalize; + font-weight: 500; + font-size: .9rem; + color: oklch(55.17% 0.014 285.94); + } + b{ + font-size: 1.5rem; + line-height: 1.2; + color: oklch(23.5% 0 0); + } + p.indicator{ + margin-top: .2rem; + span{ + width: max-content; + margin-top: .7rem; + font-size: 0.9rem; + font-weight: 600; + color: oklch(47.47% 0 0); + &.positive{ + color: oklch(63.06% 0.187 142.31); + &::before{ + content: '+'; + } + } + &.negative{ + color: oklch(63.06% 0.226 21.66); + &::before{ + content: '-'; + } + } + &::after{ + content: '%'; + } + } + } + } + figure{ + width: max-content; + height: max-content; + padding: .5rem; + border-radius: 50%; + background-color: oklch(59.99% 0 0); + color: oklch(100% 0 0); + svg{ + width: 2.3rem; + height: 2.3rem; + } + } +} \ No newline at end of file diff --git a/src/components/molecules/cards/table-payments-card/index.tsx b/src/components/molecules/cards/table-payments-card/index.tsx new file mode 100644 index 0000000..e24fcb5 --- /dev/null +++ b/src/components/molecules/cards/table-payments-card/index.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import style from './styles.module.scss' +import classNames from 'classnames' +import { type Movement } from '@/types' +import { Currency } from '@/const/DashboardConst' + +interface Props { + title: string + classname?: string + history: Movement[] +} + +export default function TablePaymentsCard ({ title, classname, history }: Props): JSX.Element { + const classes = classNames(style.TablePayments, classname) + + return ( +
+

{title}

+ + + {history.slice(0, 5).map((m, i) => { + return ( + + + + + ) + })} + +
+

{m.to}

+

{m.date.toLocaleDateString('es', { weekday: 'long', year: 'numeric', month: 'short', day: 'numeric' })}

+
{Currency}${m.value}
+
+ ) +} diff --git a/src/components/molecules/cards/table-payments-card/styles.module.scss b/src/components/molecules/cards/table-payments-card/styles.module.scss new file mode 100644 index 0000000..b1a0daf --- /dev/null +++ b/src/components/molecules/cards/table-payments-card/styles.module.scss @@ -0,0 +1,71 @@ +.TablePayments{ + padding: 1rem 1.25rem; + background-color: oklch(100% 0 0); + box-shadow: 0 0 2rem 0 rgba(136,152,170,.15); + border-radius: 1rem; + position: relative; + h2{ + text-transform: capitalize; + font-weight: 500; + font-size: 1.1rem; + padding: .2rem 0; + } + table{ + width: 100%; + height: max-content; + margin: .3rem auto 0; + tr{ + height: 3rem; + td{ + padding: 0 1rem; + &:first-child{ + h3{ + text-transform: capitalize; + font-weight: 600; + color: oklch(33.28% 0 260.51); + } + p{ + font-size: .8rem; + } + } + &:last-child{ + text-align: end; + } + } + &.positive td:last-child{ + font-size: .9rem; + font-weight: 500; + color: oklch(63.06% 0.187 142.31); + &::before{ + content: '+'; + margin-right: .2rem; + } + } + &.negative td:last-child{ + font-size: .9rem; + font-weight: 500; + color: oklch(63.06% 0.226 21.66); + &::before{ + content: '-'; + margin-right: .2rem; + } + } + &:not(:last-child){ + border-bottom: 2px solid #e9ecef; + } + &:hover{ + background-color: rgba(146, 146, 146, 0.101); + cursor: pointer; + } + } + } + &:has(table tbody:empty)::after{ + content: 'No hubo movimientos recientes'; + color: rgb(33, 33, 33); + text-align: center; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } +} \ No newline at end of file diff --git a/src/components/molecules/charts/expense-chart/index.tsx b/src/components/molecules/charts/expense-chart/index.tsx new file mode 100644 index 0000000..b0533b1 --- /dev/null +++ b/src/components/molecules/charts/expense-chart/index.tsx @@ -0,0 +1,81 @@ +'use client' + +import React from 'react' +import { Line } from 'react-chartjs-2' +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +} from 'chart.js' + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend +) + +const labels = ['Domingo', 'Lunes', 'Martes', 'Jueves', 'Viernes', 'Sabado'] + +export const data = { + type: 'line', + labels, + datasets: [ + { + label: 'Ingresos', + data: labels.map(() => 2), + borderColor: 'rgb(53, 162, 235)', + backgroundColor: 'rgba(53, 162, 235, 0.5)' + }, + { + label: 'Gastos', + data: labels.map(() => 2), + borderColor: 'rgb(255, 99, 132)', + backgroundColor: 'rgba(255, 99, 132, 0.5)' + } + ] +} + +interface Props { + className?: string +} + +export default function ExpenseChart ({ className }: Props): JSX.Element { + return ( + + ) +} diff --git a/src/components/pages/layouts/dashboard-layout/index.tsx b/src/components/pages/layouts/dashboard-layout/index.tsx index 5f9885b..a983d52 100644 --- a/src/components/pages/layouts/dashboard-layout/index.tsx +++ b/src/components/pages/layouts/dashboard-layout/index.tsx @@ -1,8 +1,10 @@ +'use client' + import style from './styles.module.scss' import Text from '@/components/atoms/text/Text' import Breadcrumb from '@/components/molecules/breadcrumb' import Image from 'next/image' -import React from 'react' +import React, { useContext } from 'react' import ButtonWithPopover, { CardLinkPopover } from '@/components/molecules/buttons/button-with-popover' @@ -10,12 +12,15 @@ import ProfileButton from '@/components/molecules/buttons/profile-button' import NavDisclosure from '@/components/molecules/disclosures/nav-disclosure' import { AsideIcons, NavIcons, NavLinks } from '@/const/DashboardConst' import CurrentLocation from '@/components/atoms/text/CurrentLocation' +import { userContext } from '@/context/userContext' interface Props { children: React.ReactNode } export default function DashboardLayout ({ children }: Props): JSX.Element { + const user = useContext(userContext) + return (