diff --git a/pages/api/ranking/contants.ts b/pages/api/ranking/contants.ts new file mode 100644 index 0000000..5b3c230 --- /dev/null +++ b/pages/api/ranking/contants.ts @@ -0,0 +1 @@ +export const RANKING_INITIAL_DATA = new Date("2024-06-10"); diff --git a/pages/api/ranking/degrees/[degree]/[class]/index.ts b/pages/api/ranking/degrees/[degree]/[class]/index.ts new file mode 100644 index 0000000..f112166 --- /dev/null +++ b/pages/api/ranking/degrees/[degree]/[class]/index.ts @@ -0,0 +1,82 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import runRequestWithDIContainer from "../../../../../../middlewares/diContainerMiddleware"; +import { PrismaClient } from "@prisma/client"; +import { DIContainerNextApiRequest } from "../../../../../../dependency_injection/DIContainerNextApiRequest"; +import { RANKING_INITIAL_DATA } from "../../../contants"; +import { GetClassData } from "../../../types"; + +export default async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === "GET") { + await runRequestWithDIContainer(req, res, run); + } else { + res.statusCode = 405; + res.send(""); + } +}; + +async function run(req: DIContainerNextApiRequest, res: NextApiResponse) { + const prismaClient: PrismaClient = req.scope.resolve("dbClient"); + try { + const rankingData = await getData( + prismaClient, + req.query.degree as string, + req.query.class as string + ); + res.statusCode = 200; + res.json(rankingData); + } catch (error) { + console.log(error); + res.statusCode = 500; + return; + } +} + +async function getData( + prisma: PrismaClient, + degree: string, + year: string +): Promise { + const minYear = Math.floor(Number(year) / 5) * 5; + const maxYear = minYear + 5; + + const result: { + period: number; + degree: string; + amount: number; + donors: number; + first_name: string; + last_name: string; + url: string; + }[] = await prisma.$queryRaw` + SELECT + u."id", + u."first_name", + u."last_name", + u."url", + SUM(c."amount_in_cents")/100 AS "amount" + FROM "users" u + LEFT JOIN "contributions" c ON u."id" = c."userId" + WHERE + u."degree" = ${degree} + AND u."admission_year" >= ${minYear} + AND u."admission_year" < ${maxYear} + AND c."state" = 'completed' + AND c."createdAt" > ${RANKING_INITIAL_DATA} + GROUP BY u."id" + `; + + const amount = result.reduce((acc, row) => acc + row.amount, 0); + const numberOfDonors = result.length; + const donors: GetClassData["donors"] = result.map((row, index) => { + return { + name: `${row.first_name} ${row.last_name}`, + url: row.url, + }; + }); + + return { + amount, + numberOfDonors, + donors, + }; +} diff --git a/pages/api/ranking/degrees/[degree]/index.ts b/pages/api/ranking/degrees/[degree]/index.ts new file mode 100644 index 0000000..3486cfa --- /dev/null +++ b/pages/api/ranking/degrees/[degree]/index.ts @@ -0,0 +1,77 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import runRequestWithDIContainer from "../../../../../middlewares/diContainerMiddleware"; +import { PrismaClient } from "@prisma/client"; +import { DIContainerNextApiRequest } from "../../../../../dependency_injection/DIContainerNextApiRequest"; +import { RANKING_INITIAL_DATA } from "./../../contants"; +import { GetRankingData } from "./../../types"; + +export default async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === "GET") { + await runRequestWithDIContainer(req, res, run); + } else { + res.statusCode = 405; + res.send(""); + } +}; + +async function run(req: DIContainerNextApiRequest, res: NextApiResponse) { + const prismaClient: PrismaClient = req.scope.resolve("dbClient"); + try { + console.log(req.query); + const rankingData = await getRankingData( + prismaClient, + req.query.degree as string | undefined + ); + res.statusCode = 200; + res.json(rankingData); + } catch (error) { + console.log(error); + res.statusCode = 500; + return; + } +} + +async function getRankingData( + prisma: PrismaClient, + degree: string | undefined +): Promise { + const result: { + period: number; + degree: string; + amount: number; + donors: number; + }[] = await prisma.$queryRaw` + SELECT + FLOOR((u."admission_year") / 5) * 5 AS "period", + u."degree", + SUM(c."amount_in_cents")/100 AS "amount", + COUNT(DISTINCT c."userId") AS "donors" + FROM "users" u + JOIN "contributions" c ON u."id" = c."userId" + WHERE + c."state" = 'completed' + AND c."createdAt" > ${RANKING_INITIAL_DATA} + AND u."degree" = ${degree} + GROUP BY "period", u."degree" + ORDER BY "amount" DESC; + `; + + const amount = result.reduce((acc, row) => acc + row.amount, 0); + const numberOfDonors = result.reduce((acc, row) => acc + row.donors, 0); + const ranking: GetRankingData["ranking"] = result.map((row, index) => { + return { + position: index + 1, + degree: row.degree, + initialYear: row.period, + finalYear: row.period + 4, + amount: row.amount, + numberOfDonors: row.donors, + }; + }); + + return { + amount, + numberOfDonors, + ranking, + }; +} diff --git a/pages/api/ranking/index.ts b/pages/api/ranking/index.ts new file mode 100644 index 0000000..10bd8b5 --- /dev/null +++ b/pages/api/ranking/index.ts @@ -0,0 +1,76 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import runRequestWithDIContainer from "../../../middlewares/diContainerMiddleware"; +import { PrismaClient } from "@prisma/client"; +import { DIContainerNextApiRequest } from "../../../dependency_injection/DIContainerNextApiRequest"; +import { RANKING_INITIAL_DATA } from "./contants"; +import { GetRankingData } from "./types"; + +export default async (req: NextApiRequest, res: NextApiResponse) => { + if (req.method === "GET") { + await runRequestWithDIContainer(req, res, run); + } else { + res.statusCode = 405; + res.send(""); + } +}; + +async function run(req: DIContainerNextApiRequest, res: NextApiResponse) { + const prismaClient: PrismaClient = req.scope.resolve("dbClient"); + try { + console.log(req.query); + const rankingData = await getRankingData(prismaClient); + res.statusCode = 200; + res.json(rankingData); + } catch (error) { + console.log(error); + res.statusCode = 500; + return; + } +} + +const MOCK: GetRankingData = { + amount: 100000, + numberOfDonors: 100, + ranking: [], +}; + +async function getRankingData(prisma: PrismaClient): Promise { + const result: { + period: number; + degree: string; + amount: number; + donors: number; + }[] = await prisma.$queryRaw` + SELECT + FLOOR((u."admission_year") / 5) * 5 AS "period", + u."degree", + SUM(c."amount_in_cents")/100 AS "amount", + COUNT(DISTINCT c."userId") AS "donors" + FROM "users" u + JOIN "contributions" c ON u."id" = c."userId" + WHERE + c."state" = 'completed' + AND c."createdAt" > ${RANKING_INITIAL_DATA} + GROUP BY "period", u."degree" + ORDER BY "amount" DESC; + `; + + const amount = result.reduce((acc, row) => acc + row.amount, 0); + const numberOfDonors = result.reduce((acc, row) => acc + row.donors, 0); + const ranking: GetRankingData["ranking"] = result.map((row, index) => { + return { + position: index + 1, + degree: row.degree, + initialYear: row.period, + finalYear: row.period + 4, + amount: row.amount, + numberOfDonors: row.donors, + }; + }); + + return { + amount, + numberOfDonors, + ranking, + }; +} diff --git a/pages/api/ranking/types.ts b/pages/api/ranking/types.ts new file mode 100644 index 0000000..0c2dcd3 --- /dev/null +++ b/pages/api/ranking/types.ts @@ -0,0 +1,21 @@ +export type GetRankingData = { + amount: number; + numberOfDonors: number; + ranking: { + position: number; + degree: string; + initialYear: number; + finalYear: number; + amount: number; + numberOfDonors: number; + }[]; +}; + +export type GetClassData = { + amount: number; + numberOfDonors: number; + donors: { + name: string; + url?: string; + }[]; +}; diff --git a/pages/api/registration/index.ts b/pages/api/registration/index.ts index a342c2d..f91ee67 100644 --- a/pages/api/registration/index.ts +++ b/pages/api/registration/index.ts @@ -17,6 +17,7 @@ const CreateUserSchema = schema({ degree: string, admissionYear: string, dob: string, + url: string.optional(), tutorshipInterest: Boolean, mentorshipInterest: Boolean, volunteeringInterest: Boolean, @@ -69,6 +70,7 @@ async function runCreateUser( tutorshipInterest: args.tutorshipInterest, mentorshipInterest: args.mentorshipInterest, volunteeringInterest: args.volunteeringInterest, + url: args.url, }); if (!user) { throw new Error(messages.USER_REGISTRATION_FAILED); diff --git a/pages/ranking/groups/[:groupId]/index.module.css b/pages/ranking/degrees/[degree]/[year]/index.module.css similarity index 100% rename from pages/ranking/groups/[:groupId]/index.module.css rename to pages/ranking/degrees/[degree]/[year]/index.module.css diff --git a/pages/ranking/degrees/[degree]/[year]/index.tsx b/pages/ranking/degrees/[degree]/[year]/index.tsx new file mode 100644 index 0000000..4da40ad --- /dev/null +++ b/pages/ranking/degrees/[degree]/[year]/index.tsx @@ -0,0 +1,86 @@ +import styles from "./index.module.css"; +import Header from "../../../../../components/ranking/Header/Header"; +import DonationCallout from "../../../../../components/ranking/Donate/DonateBanner"; +import ResultsDisplay from "../../../../../components/ranking/ResultsDisplay/ResultsDisplay"; +import ShareCallout from "../../../../../components/ranking/Share/ShareCallout"; +import BackButton from "../../../../../components/ranking/BackButton/BackButton"; +import { useRouter } from "next/router"; +import { GetClassData } from "../../../../api/ranking/types"; +import { useState, useEffect } from "react"; + +const getClassData = async (degree: string, year: string) => { + const response = await fetch(`/api/ranking/degrees/${degree}/${year}`); + return (await response.json()) as GetClassData; +}; + +export default function ClassPage() { + const [data, setData] = useState({ + amount: 0, + numberOfDonors: 0, + donors: [], + }); + + const donated = true; + + const router = useRouter(); + const degree = router.query.degree as string; + const year = router.query.year as string; + + useEffect(() => { + getClassData(degree, year).then(setData); + }); + + if (Number(year) < 1900) { + return

Invalid year

; + } + + return ( +
+
+ +
+ +
{ + window.open("/ranking/degrees/" + degree, "_self"); + }} + title={year + "-" + String(Number(year) + 4)} + description={"Any"} + /> + + + + {!donated && ( +

Você precisa doar para poder visualizar aos doadores

+ )} + + +
+
+
+ ); +} + +const Donors = ({ + userDonated, + donors, +}: { + userDonated: boolean; + donors: { name: string; url?: string }[]; +}) => { + return ( +
+ {donors.map((donor) => ( + + ))} +
+ ); +}; diff --git a/pages/ranking/degrees/[degree]/index.module.css b/pages/ranking/degrees/[degree]/index.module.css new file mode 100644 index 0000000..6be6e65 --- /dev/null +++ b/pages/ranking/degrees/[degree]/index.module.css @@ -0,0 +1,62 @@ +.html { + font-family: "Open Sans", sans-serif; + background-color: rgb(255, 255, 255); + text-align: center; +} + +.content { + padding: 80px 20px; +} + +.rankingPage { + display: flex; + align-items: center; + flex-direction: column; +} + +/* Table */ + +.groupsTable { + width: 100%; + max-width: 600px; + margin-top: 50px; + background-color: #ffffff; + font-size: 14px; + padding: 20px; + border: 7px solid rgb(242, 242, 242); + border-radius: 10px; + height: 600px; + overflow-y: scroll; + overflow-x: hidden; + + /* scrollbar-width: none; */ + scrollbar-color: rgb(202, 202, 202) rgba(255, 255, 255, 0); + scrollbar-width: thin; +} + +.groupsTable table { + width: 100%; + border-collapse: collapse; +} + +.groupsTable th { + height: 60px; +} + +.groupsTable tbody { + max-height: 300px !important; + overflow: scroll; + width: 100%; +} + +.groupsTable td { + border-top: 1px solid rgb(202, 202, 202); + text-align: center; + height: 60px; + cursor: pointer; +} + +.groupsTable tr:hover { + background-color: rgb(239, 254, 255); +} + diff --git a/pages/ranking/degrees/[degree]/index.tsx b/pages/ranking/degrees/[degree]/index.tsx new file mode 100644 index 0000000..265443e --- /dev/null +++ b/pages/ranking/degrees/[degree]/index.tsx @@ -0,0 +1,93 @@ +import Head from "next/head"; +import styles from "./index.module.css"; +import ShareCallout from "../../../../components/ranking/Share/ShareCallout"; +import DonationCallout from "../../../../components/ranking/Donate/DonateBanner"; +import ResultsDisplay from "../../../../components/ranking/ResultsDisplay/ResultsDisplay"; +import Header from "../../../../components/ranking/Header/Header"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import { GetRankingData } from "../../../api/ranking/types"; + +const getRankingData = async (degree: string) => { + const response = await fetch(`/api/ranking/degrees/${degree}`); + return (await response.json()) as GetRankingData; +}; + +export default function DegreeRankingPage() { + const router = useRouter(); + const degree = router.query.degree as string; + const [data, setData] = useState({ + amount: 0, + numberOfDonors: 0, + ranking: [], + }); + + useEffect(() => { + getRankingData(degree).then(setData); + }, [router.query.degree]); + + return ( +
+ + Ranking de Turmas + + + + + +
+ +
+
{ + window.open("/ranking", "_self"); + }} + title={degree} + description="Você pode visualizar as turmas que mais contribuíram e verificar se a sua está entre elas! Uma visão geral do impacto nas receitas só está disponível mediante a realização de uma doação." + /> + + + + + + + ); +} + +const Table = (props: { ranking: GetRankingData["ranking"] }) => { + return ( +
+
+ + + + + + + {props.ranking.map((row, index) => ( + { + window.open( + `/ranking/degrees/${row.degree}/${row.initialYear}`, + "_self" + ); + }} + > + + + + + ))} + +
RankTurmaValor de doação (R$)
#{row.position}{`${row.initialYear} - ${row.finalYear}`}{new Intl.NumberFormat("pt-BR", {}).format(row.amount)}
+
+ ); +}; diff --git a/pages/ranking/form/index.module.css b/pages/ranking/form/index.module.css deleted file mode 100644 index f23bb85..0000000 --- a/pages/ranking/form/index.module.css +++ /dev/null @@ -1,194 +0,0 @@ -.html { - font-family: "Open Sans", sans-serif; - background-color: rgb(255, 255, 255); - text-align: center; -} - -.content { - padding: 80px 20px; -} - -.rankingPage { - display: flex; - align-items: center; - flex-direction: column; -} - -/* Title Container */ - -.titleContainer { - max-width: 600px; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - text-align: center; - gap: 20px; -} - -.titleContainer h1 { - margin: 0; - font-size: 60px; - font-weight: 900; - z-index: 3; -} - -.titleContainer p { - margin: 0; -} - -.reditusButton { - display: flex; - align-items: center; - justify-content: center; - background-color: rgb(240, 212, 72); - color: rgb(37, 19, 0); - border: none; - border-radius: 100px; - cursor: pointer; - padding: 10px 30px; - font-weight: 700; - font-size: 16px; - transform: scale(1.1) translateY(10px); - z-index: 1; -} - -/* Donation Callout */ - - -.donationCallout { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 20px; - background-color: #90f2ff; - width: 100%; - padding: 10px 40px; - border-bottom: 1px solid #ffffff; -} - -.donationCallout p { - margin: 0; - font-weight: 600; - color: rgb(29, 118, 145); -} - -.donationButton { - display: flex; - align-items: center; - justify-content: center; - background-color: rgb(29, 118, 145); - color: rgb(255, 255, 255); - border: none; - border-radius: 100px; - cursor: pointer; - padding: 10px 30px; - font-weight: 700; - font-size: 16px; -} - -.donationButton:hover { - background-color: rgb(14, 88, 111); -} - -/* Results Pannel */ - -.resultsPannel { - display: flex; - flex-direction: row; - justify-content: space-evenly; - align-items: center; - gap: 20px; - background-color: #224347; - padding: 20px 40px; - border-radius: 10px; - width: 100%; - max-width: 600px; - margin-top: 50px; - color: rgb(255, 255, 255); -} - -.resultsPannel p { - font-size: xx-large; - margin: 0; - font-weight: 700; -} - -.resultsPannel .divider { - width: 2px; - height: 50px; - background-color: white; -} - -/* Share Callout */ - -.shareCallout { - display: flex; - flex-direction: row; - justify-content: center; - align-items: center; - gap: 20px; - background-color: #90f2ff; - padding: 40px 40px; - border-radius: 100px; - margin-top: 50px; -} - -.shareBtn { - display: flex; - align-items: center; - justify-content: center; - background-color: rgb(255, 255, 255); - color: rgb(0, 0, 0); - border: none; - border-radius: 100px; - width: 50px; - height: 50px; - cursor: pointer; -} - -.shareBtn:hover { - background-color: rgb(218, 242, 255); -} - -/* Table */ - -.groupsTable { - width: 100%; - max-width: 600px; - margin-top: 50px; - overflow: hidden; - background-color: #ffffff; - font-size: 14px; - padding: 20px; - border: 1px solid rgb(202, 202, 202); - border-radius: 10px; -} - -.groupsTable table { - width: 100%; - border-collapse: collapse; -} - -.groupsTable th { - height: 60px; -} - -.groupsTable tbody { - height: 300px !important; - overflow: scroll; - width: 100%; -} - -.groupsTable td { - border-top: 1px solid rgb(202, 202, 202); - text-align: center; - height: 60px; - cursor: pointer; -} - -.groupsTable tr:hover { - background-color: rgb(239, 254, 255); -} - diff --git a/pages/ranking/form/index.tsx b/pages/ranking/form/index.tsx deleted file mode 100644 index 19d5832..0000000 --- a/pages/ranking/form/index.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import Head from "next/head"; -import styles from "./index.module.css"; -import WhatsApp from "@material-ui/icons/WhatsApp"; -import LinkedIn from "@material-ui/icons/LinkedIn"; -import Send from "@material-ui/icons/Send"; - -export default function RankingPage() { - return ( -
- - Ranking de Turmas - - - - - -
- -
- - <Form /> - <ShareCallout /> - </div> - </main> - </div> - ); -} - -const Title = () => { - return ( - <div className={styles.titleContainer}> - <button - className={styles.reditusButton} - onClick={() => { - window.open("https://www.reditus.org.br/", "_blank")?.focus(); - }} - > - Instituto Reditus - </button> - <h1>Ranking de Turmas</h1> - <p> - Você pode visualizar as turmas que mais contribuíram e verificar se a - sua está entre elas! Uma visão geral do impacto nas receitas só está - disponível mediante a realização de uma doação. - </p> - </div> - ); -}; - -const Form = () => { - return ( - <div className={styles.form}> - <h2>Informe o seu nome</h2> - <input type="text" placeholder="Nome" /> - - <h2>Data de nascimento</h2> - <input type="date" /> - - <h2>Informe o seu LinkedIn (ou site)</h2> - <input type="email" placeholder="LinkedIn" /> - - <h2>Selecione o seu curso</h2> - <select> - <option value="1">Engenharia de Software</option> - <option value="2">Ciência da Computação</option> - <option value="3">Engenharia de Computação</option> - </select> - - <h2>Selecione seu ano</h2> - <select> - <option value="1">2020-2025</option> - <option value="2">2015-2020</option> - <option value="3">2010-2015</option> - </select> - - <h2>Informe o valor da sua doação</h2> - <input type="number" placeholder="R$ 0,00" /> - - <h2>Quer doar recorrentemente?</h2> - <input type="checkbox" /> - - <button className={styles.donationButton}>Doar agora</button> - </div> - ); -}; - -const DonationCallout = () => { - return ( - <div className={styles.donationCallout}> - <p>Doe para que possamos continuar ajudando!</p> - <button className={styles.donationButton}>Doar agora!</button> - </div> - ); -}; - -const ResultsPannel = () => { - return ( - <div className={styles.resultsPannel}> - <p>R$ 12.300,39</p> - </div> - ); -}; - -const Table = () => { - const rows: { - rank: number; - group: string; - donation: string; - }[] = []; - - for (let i = 0; i < 30; i++) { - rows.push({ - rank: i + 1, - group: `Turma ${i + 1}`, - donation: `R$ ${Math.floor(Math.random() * 1000 + 100)}`, - }); - } - - return ( - <div className={styles.groupsTable}> - <table> - <thead> - <th>Rank</th> - <th>Turma</th> - <th>Valor de doação</th> - </thead> - <tbody> - {rows.map((row, index) => ( - <tr - key={index} - onClick={() => { - window.location.href = `/ranking/groups/${row.group}`; - }} - > - <td style={{ fontWeight: "bold" }}>#{row.rank}</td> - <td>{row.group}</td> - <td>{row.donation}</td> - </tr> - ))} - </tbody> - </table> - </div> - ); -}; - -const ShareCallout = () => { - return ( - <div className={styles.shareCallout}> - <button className={styles.donationButton}>Doar agora</button>{" "} - <button - className={styles.shareBtn} - onClick={() => { - window.open( - "https://api.whatsapp.com/send?text=Olá! Estou participando do ranking de turmas do Instituto Reditus e gostaria de te convidar a participar também! Acesse o link e veja como você pode ajudar: https://www.reditus.org.br/ranking", - "_blank" - ); - }} - > - <WhatsApp /> - </button> - <button - className={styles.shareBtn} - onClick={() => { - window.open( - "https://www.linkedin.com/shareArticle?mini=true&url=https://www.reditus.org.br/ranking&title=Ranking de Turmas&summary=Olá! Estou participando do ranking de turmas do Instituto Reditus e gostaria de te convidar a participar também!&source=www.reditus.org.br", - "_blank" - ); - }} - > - <LinkedIn /> - </button> - <button - className={styles.shareBtn} - onClick={() => { - // copy to clipboard - navigator.clipboard.writeText( - "Estou participando do ranking de turmas do Instituto Reditus e gostaria de te convidar a participar também! Acesse o link e veja como você pode ajudar: https://www.reditus.org.br/ranking" - ); - }} - > - <Send /> - </button> - </div> - ); -}; diff --git a/pages/ranking/groups/[:groupId]/index.tsx b/pages/ranking/groups/[:groupId]/index.tsx deleted file mode 100644 index 7004cc4..0000000 --- a/pages/ranking/groups/[:groupId]/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import styles from "./index.module.css"; -import Header from "../../../../components/ranking/Header/Header"; -import DonationCallout from "../../../../components/ranking/Donate/DonateBanner"; -import ResultsDisplay from "../../../../components/ranking/ResultsDisplay/ResultsDisplay"; -import ShareCallout from "../../../../components/ranking/Share/ShareCallout"; -import BackButton from "../../../../components/ranking/BackButton/BackButton"; - -const group = { - course: "Engenharia de Produção", - period: "2000-2005", - description: - "Você pode visualizar as turmas que mais contribuíram e verificar se a sua está entre elas! Uma visão geral do impacto nas receitas só está disponível mediante a realização de uma doação.", - total: 5000, - donors: [ - { - name: "João da Silva", - url: "https://www.linkedin.com/in/joao-da-silva", - }, - { - name: "João da Silva", - url: "https://www.linkedin.com/in/joao-da-silva", - }, - { - name: "João da Silva", - url: "https://www.linkedin.com/in/joao-da-silva", - }, - { - name: "João da Silva", - url: "https://www.linkedin.com/in/joao-da-silva", - }, - ], -}; - -export default function RankingPage() { - const donated = true; - - return ( - <div className={styles.html}> - <main className={styles.rankingPage}> - <DonationCallout /> - <div className={styles.content}> - <BackButton /> - <Header - tag={group.course} - title={group.period} - description={group.description} - /> - <ResultsDisplay amount={group.total} count={group.donors.length} /> - <Donors userDonated={donated} donors={group.donors} /> - - {!donated && ( - <p>Você precisa doar para poder visualizar aos doadores</p> - )} - - <ShareCallout donate whatsApp linkedIn copy /> - </div> - </main> - </div> - ); -} - -const Donors = ({ - userDonated, - donors, -}: { - userDonated: boolean; - donors: { name: string; url: string }[]; -}) => { - return ( - <div className={userDonated ? styles.donors : styles.hideDonors}> - {donors.map((donor) => ( - <button - className={styles.donor} - onClick={() => { - if (userDonated && donor.url) window.open(donor.url, "_blank"); - }} - > - <p>{donor.name}</p> - </button> - ))} - </div> - ); -}; diff --git a/pages/ranking/index.tsx b/pages/ranking/index.tsx index 7d1b73a..ad27814 100644 --- a/pages/ranking/index.tsx +++ b/pages/ranking/index.tsx @@ -4,8 +4,26 @@ import ShareCallout from "../../components/ranking/Share/ShareCallout"; import DonationCallout from "../../components/ranking/Donate/DonateBanner"; import ResultsDisplay from "../../components/ranking/ResultsDisplay/ResultsDisplay"; import Header from "../../components/ranking/Header/Header"; +import { useEffect, useState } from "react"; +import { GetRankingData } from "../api/ranking/types"; export default function RankingPage() { + const [rankingData, setRankingData] = useState<GetRankingData>({ + amount: 0, + numberOfDonors: 0, + ranking: [], + }); + + const fetchRankingData = async () => { + const response = await fetch("/api/ranking"); + const data = await response.json(); + setRankingData(data); + }; + + useEffect(() => { + fetchRankingData(); + }, []); + return ( <div className={styles.html}> <Head> @@ -32,8 +50,11 @@ export default function RankingPage() { title="Ranking de Turmas" description="Você pode visualizar as turmas que mais contribuíram e verificar se a sua está entre elas! Uma visão geral do impacto nas receitas só está disponível mediante a realização de uma doação." /> - <ResultsDisplay amount={50000} count={200} /> - <Table /> + <ResultsDisplay + amount={rankingData.amount} + count={rankingData.numberOfDonors} + /> + <Table ranking={rankingData.ranking} /> <ShareCallout donate whatsApp linkedIn copy /> </div> </main> @@ -41,22 +62,10 @@ export default function RankingPage() { ); } -const Table = () => { - const rows: { - rank: number; - group: string; - donation: number; - donors: number; - }[] = []; - - for (let i = 0; i < 30; i++) { - rows.push({ - rank: i + 1, - group: `Produção ${i * 5 + 2000}`, - donation: Math.floor(Math.random() * 10000), - donors: Math.floor(Math.random() * 100), - }); - } +const Table = (props: { ranking: GetRankingData["ranking"] }) => { + const goToClass = (degree: string, year: number) => { + window.location.href = `/ranking/degrees/${degree}/${year}`; + }; return ( <div className={styles.groupsTable}> @@ -64,19 +73,19 @@ const Table = () => { <thead> <th>Rank</th> <th>Turma</th> + <th>Curso</th> <th>Valor de doação (R$)</th> </thead> <tbody> - {rows.map((row, index) => ( + {props.ranking.map((row, index) => ( <tr key={index} - onClick={() => { - window.location.href = `/ranking/groups/${row.group}`; - }} + onClick={goToClass.bind(null, row.degree, row.initialYear)} > - <td style={{ fontWeight: "bold" }}>#{row.rank}</td> - <td>{row.group}</td> - <td>{row.donation}</td> + <td style={{ fontWeight: "bold" }}>#{row.position}</td> + <td>{`${row.initialYear}-${row.finalYear}`}</td> + <td style={{ textAlign: "left" }}>{row.degree}</td> + <td>{new Intl.NumberFormat("pt-BR", {}).format(row.amount)}</td> </tr> ))} </tbody> diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9324121..abc4713 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -85,8 +85,7 @@ model User { contributions Contribution[] subscriptions ContributionSubscription[] birthday DateTime? - - url String? + url String? @@map("users") }