From 8cee54eba0cb499ea230796fdec1d145ff49f1cf Mon Sep 17 00:00:00 2001 From: creampiney Date: Sun, 29 Oct 2023 23:51:13 +0700 Subject: [PATCH 1/2] feat: review charts --- app/product/[id]/page.tsx | 2 + components/ReviewStackBar.tsx | 61 +++++++++++++++++++++++++++++ components/ui/BarChart.tsx | 73 +++++++++++++++++++++++++++++++++++ components/ui/review-box.tsx | 41 ++++++++++++++++++++ next.config.js | 2 +- package.json | 2 + pnpm-lock.yaml | 27 +++++++++++++ 7 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 components/ReviewStackBar.tsx create mode 100644 components/ui/BarChart.tsx diff --git a/app/product/[id]/page.tsx b/app/product/[id]/page.tsx index 7895b6c..4c8b0ab 100644 --- a/app/product/[id]/page.tsx +++ b/app/product/[id]/page.tsx @@ -17,6 +17,7 @@ import ReviewBox from "@/components/ui/review-box"; import ReviewForm from "@/components/ui/review_form"; import AddToCardButton from "./AddToCardButton"; import { Separator } from "@/components/ui/separator"; +import ReviewStackBar from "@/components/ReviewStackBar"; type Cascade_review = Review & { user: User; @@ -161,6 +162,7 @@ export default async function Page({ params }: { params: { id: string } }) { )} + {/*
diff --git a/components/ReviewStackBar.tsx b/components/ReviewStackBar.tsx new file mode 100644 index 0000000..e2075b8 --- /dev/null +++ b/components/ReviewStackBar.tsx @@ -0,0 +1,61 @@ +"use client"; + +import React from 'react' +import BarChart from "./ui/BarChart"; +import Chart from "chart.js/auto"; +import { Card, CardContent } from './ui/card'; +import ReactStars from 'react-stars'; +import pluralize, { plural } from 'pluralize'; + + +const ReviewStackBar = ({overallScore, ratingData} : {overallScore: {sum: number, count: number, avg: number}, ratingData: {five: number, four: number, three: number, two: number, one: number}}) => { + + const data = { + labels: ['5', '4', '3', '2', '1'], + // datasets is an array of objects where each object represents a set of data to display corresponding to the labels above. for brevity, we'll keep it at one object + datasets: [ + { + label: 'No. of reviews', + data: [ratingData?.five, ratingData?.four, ratingData?.three, ratingData?.two, ratingData?.one], + // you can set indiviual colors for each bar + backgroundColor: [ + 'rgba(255, 99, 132, 0.5)', + 'rgba(255, 99, 132, 0.5)', + 'rgba(255, 99, 132, 0.5)', + 'rgba(255, 99, 132, 0.5)', + 'rgba(255, 99, 132, 0.5)', + ], + } + ] + } + + + + return ( + + +
+
Overall Rating :
+ + + +
({(overallScore?.avg != -1.0) ? overallScore?.avg.toFixed(2) : "-"})
+
+
From {overallScore.count} {pluralize("review", overallScore.count)}
+ +
+ +
+ + + + ) +} + +export default ReviewStackBar \ No newline at end of file diff --git a/components/ui/BarChart.tsx b/components/ui/BarChart.tsx new file mode 100644 index 0000000..33fef93 --- /dev/null +++ b/components/ui/BarChart.tsx @@ -0,0 +1,73 @@ +"use client"; + +import { Bar } from "react-chartjs-2"; +import React from 'react' + +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + BarElement + } from 'chart.js' + import { Chart } from 'react-chartjs-2' + + ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, + BarElement + ) + + + +const BarChart = ({data, max} : {data : any, max: number}) => { + const options = { + indexAxis: 'y' as const, + maintainAspectRatio: false, + plugins: { + legend: { + display: false + }, + title: { + display: false + }, + + }, + scales: { + x: { + min: 0, + max: max, + grid: { + display: false + }, + display: false + }, + y: { + grid: { + display: false + } + } + }, + + } + + return ( +
+ +
+ ) +} + +export default BarChart \ No newline at end of file diff --git a/components/ui/review-box.tsx b/components/ui/review-box.tsx index 8d346ff..96aaa7c 100644 --- a/components/ui/review-box.tsx +++ b/components/ui/review-box.tsx @@ -7,6 +7,7 @@ import initials from "initials"; import { useEffect, useState } from "react"; import Link from "next/link"; import ReactStars from "react-stars"; +import ReviewStackBar from "../ReviewStackBar"; type Cascade_review = Review & { @@ -16,10 +17,49 @@ type Cascade_review = Review & { export default function ReviewBox({reviews} : {reviews : Cascade_review[]}){ // magic const [hydrated, setHydrated] = useState(false); + + const [overallScore, setOverallScore] = useState<{sum: number, count: number, avg: number}>({sum: 0, count: 0, avg: 0}); + const [ratingData, setRatingData] = useState<{five: number, four: number, three: number, two: number, one: number}>({five: 0, four: 0, three: 0, two: 0, one: 0}) + + function getOverallScore() { + var data = {five: 0, four: 0, three: 0, two: 0, one: 0} + for (var e of reviews) { + if (e.score == 5) { + data.five += 1 + } + else if (e.score == 4) { + data.four += 1 + } + else if (e.score == 3) { + data.three += 1 + } + else if (e.score == 2) { + data.two += 1 + } + else if (e.score == 1) { + data.one += 1 + } + } + const count = data.five + data.four + data.three + data.two + data.one + const sum = 5*data.five + 4*data.four + 3*data.three + 2*data.two + 1*data.one + + var avg = -1.0 + if (count != 0) { + avg = sum/count + } + + setOverallScore({sum: sum, count: count, avg: avg}) + setRatingData(data) + } useEffect(() => { setHydrated(true); }, []); + + useEffect(() => { + getOverallScore(); + }, []); + if (!hydrated) { // Returns null on first render, so the client and server match return null; @@ -27,6 +67,7 @@ export default function ReviewBox({reviews} : {reviews : Cascade_review[]}){ // return(<> + {(reviews.length !== 0) && } {(reviews.length !== 0) ? (reviews.map((r) => (
diff --git a/next.config.js b/next.config.js index 21b90e9..4763c1b 100644 --- a/next.config.js +++ b/next.config.js @@ -1,7 +1,7 @@ /** @type {import('next').NextConfig} */ const nextConfig = { images: { - domains: ["lh3.googleusercontent.com","res.cloudinary.com","utfs.io"], + domains: ["lh3.googleusercontent.com","res.cloudinary.com","utfs.io","images.unsplash.com","plus.unsplash.com"], }, } diff --git a/package.json b/package.json index 54ca105..a114c52 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-toast": "^1.1.5", "@uploadthing/react": "^5.7.0", + "chart.js": "^4.4.0", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", "cmdk": "^0.2.0", @@ -35,6 +36,7 @@ "next-themes": "^0.2.1", "pluralize": "^8.0.0", "react": "^18", + "react-chartjs-2": "^5.2.0", "react-dom": "^18", "react-hook-form": "^7.47.0", "react-stars": "^2.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ab0654..63674f1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ dependencies: '@uploadthing/react': specifier: ^5.7.0 version: 5.7.0(next@13.5.6)(react@18.2.0)(uploadthing@5.7.2)(zod@3.22.4) + chart.js: + specifier: ^4.4.0 + version: 4.4.0 class-variance-authority: specifier: ^0.7.0 version: 0.7.0 @@ -80,6 +83,9 @@ dependencies: react: specifier: ^18 version: 18.2.0 + react-chartjs-2: + specifier: ^5.2.0 + version: 5.2.0(chart.js@4.4.0)(react@18.2.0) react-dom: specifier: ^18 version: 18.2.0(react@18.2.0) @@ -359,6 +365,10 @@ packages: uncrypto: 0.1.3 dev: false + /@kurkle/color@0.3.2: + resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==} + dev: false + /@next/env@12.3.0: resolution: {integrity: sha512-PTJpjAFVbzBQ9xXpzMTroShvD5YDIIy46jQ7d4LrWpY+/5a8H90Tm8hE3Hvkc5RBRspVo7kvEOnqQms0A+2Q6w==} dev: false @@ -1996,6 +2006,13 @@ packages: supports-color: 7.2.0 dev: true + /chart.js@4.4.0: + resolution: {integrity: sha512-vQEj6d+z0dcsKLlQvbKIMYFHd3t8W/7L2vfJIbYcfyPcRx92CsHqECpueN8qVGNlKyDcr5wBrYAYKnfu/9Q1hQ==} + engines: {pnpm: '>=7'} + dependencies: + '@kurkle/color': 0.3.2 + dev: false + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -3742,6 +3759,16 @@ packages: strip-json-comments: 2.0.1 dev: false + /react-chartjs-2@5.2.0(chart.js@4.4.0)(react@18.2.0): + resolution: {integrity: sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==} + peerDependencies: + chart.js: ^4.1.1 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + chart.js: 4.4.0 + react: 18.2.0 + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: From b99e54d4b186bf7ec479df493219bed7cc692562 Mon Sep 17 00:00:00 2001 From: creampiney Date: Sun, 29 Oct 2023 23:51:40 +0700 Subject: [PATCH 2/2] fix: typo --- app/product/[id]/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/product/[id]/page.tsx b/app/product/[id]/page.tsx index 4c8b0ab..5e8ca6e 100644 --- a/app/product/[id]/page.tsx +++ b/app/product/[id]/page.tsx @@ -192,7 +192,7 @@ export default async function Page({ params }: { params: { id: string } }) {
- Review section + Review Section
{isAuthenticated() ? (