diff --git a/public/icons/arrowUp.svg b/public/icons/arrowUp.svg new file mode 100644 index 000000000..1aac505ad --- /dev/null +++ b/public/icons/arrowUp.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/box.svg b/public/icons/box.svg new file mode 100644 index 000000000..b76411d20 --- /dev/null +++ b/public/icons/box.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/icons/dollarSign.svg b/public/icons/dollarSign.svg new file mode 100644 index 000000000..901091cf1 --- /dev/null +++ b/public/icons/dollarSign.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/icons/user.svg b/public/icons/user.svg new file mode 100644 index 000000000..9d4877530 --- /dev/null +++ b/public/icons/user.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/app/(admin)/admin/dashboard/client.tsx b/src/app/(admin)/admin/dashboard/client.tsx new file mode 100644 index 000000000..f0f585ad3 --- /dev/null +++ b/src/app/(admin)/admin/dashboard/client.tsx @@ -0,0 +1,52 @@ +"use client"; + +import Link from "next/link"; + +import CardComponent from "~/components/adminDashboard/CardComponent"; +import { cardData } from "~/components/adminDashboard/cardData"; +import { Chart } from "~/components/adminDashboard/Chart"; +import { chartConfig, chartData } from "~/components/adminDashboard/chartData"; +import { data, gradients } from "~/components/adminDashboard/productData"; +import TopProductsComponent from "~/components/adminDashboard/TopProductsComponent"; +import { Card } from "~/components/ui/card"; + +const Client = () => { + return ( +
+
+
+ +

Overview

+ +

+ Showing records from the last ..... +

+
+
+ +
+ {cardData.map((card, index) => ( + + ))} +
+ +
+ +

+ Overview +

+ +
+ +
+
+ ); +}; + +export default Client; diff --git a/src/app/(admin)/admin/dashboard/page.tsx b/src/app/(admin)/admin/dashboard/page.tsx new file mode 100644 index 000000000..1f476ebc5 --- /dev/null +++ b/src/app/(admin)/admin/dashboard/page.tsx @@ -0,0 +1,13 @@ +import Client from "./client"; + +export const metadata = { + title: "Dashboard", + description: + "Super Admin Dashboard using ShadCN UI components, adhering to design specifications and best practices for accessibility.", +}; + +const AdminDashboardPage = () => { + return ; +}; + +export default AdminDashboardPage; diff --git a/src/app/guides/page.tsx b/src/app/guides/page.tsx index 079865a68..df6dbf539 100644 --- a/src/app/guides/page.tsx +++ b/src/app/guides/page.tsx @@ -317,7 +317,7 @@ const StyleGuide: FC = () => {

Input Components

= ({ + title, + value, + description, + icon, +}) => { + return ( + + + + {title} + {title} + + + +

{value}

+

{description}

+
+
+ ); +}; + +export default CardComponent; diff --git a/src/components/adminDashboard/Chart.tsx b/src/components/adminDashboard/Chart.tsx new file mode 100644 index 000000000..52f0a9f6a --- /dev/null +++ b/src/components/adminDashboard/Chart.tsx @@ -0,0 +1,58 @@ +import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; + +import { CardContent } from "~/components/ui/card"; +import { + ChartConfig, + ChartContainer, + ChartTooltip, + ChartTooltipContent, +} from "~/components/ui/chart"; + +type ChartProperties = { + chartData: { month: string; revenue: number }[]; + chartConfig: ChartConfig; +}; + +export function Chart({ chartData = [], chartConfig }: ChartProperties) { + return ( + <> +
+ + + + + value.slice(0, 3)} + /> + `$${value}`} + /> + } + /> + + + + +
+ + ); +} diff --git a/src/components/adminDashboard/TopProductsComponent.tsx b/src/components/adminDashboard/TopProductsComponent.tsx new file mode 100644 index 000000000..1f73e6050 --- /dev/null +++ b/src/components/adminDashboard/TopProductsComponent.tsx @@ -0,0 +1,91 @@ +import React from "react"; + +import { Card } from "../ui/card"; + +type ProductData = { + name: string; + amount: string; +}; + +type TopProductsProperties = { + data: ProductData[]; + gradients: string[]; +}; + +const TopProductsComponent: React.FC = ({ + data, + gradients, +}) => { + return ( + +
+
+

+ Top Products +

+

+ Your top selling products appear here. +

+
+ +
+
    + {data.map((item, index) => ( +
  • +
    +
    +
    +

    + {item.name} +

    +
    +
    +

    + {item.amount} +

    +
  • + ))} +
+
+ ); +}; + +export default TopProductsComponent; diff --git a/src/components/adminDashboard/cardData.ts b/src/components/adminDashboard/cardData.ts new file mode 100644 index 000000000..9e24a891f --- /dev/null +++ b/src/components/adminDashboard/cardData.ts @@ -0,0 +1,33 @@ +type CardData = { + title: string; + value: string; + description: string; + icon: string; +}; + +export const cardData: CardData[] = [ + { + title: "Total Revenue", + value: "$45,000.00", + description: "+20% from last month", + icon: `/icons/dollarSign.svg`, + }, + { + title: "Total Users", + value: "+4,000", + description: "+10% from last month", + icon: `/icons/user.svg`, + }, + { + title: "Total Products", + value: "1,000", + description: "+20% from last month", + icon: `/icons/box.svg`, + }, + { + title: "Lifetime Sales", + value: "$450,000.00", + description: "+150% from last month", + icon: `/icons/arrowUp.svg`, + }, +]; diff --git a/src/components/adminDashboard/chartData.ts b/src/components/adminDashboard/chartData.ts new file mode 100644 index 000000000..4314df6e2 --- /dev/null +++ b/src/components/adminDashboard/chartData.ts @@ -0,0 +1,30 @@ +import { ChartConfig } from "~/components/ui/chart"; + +export const generateRandomRevenue = () => + (Math.floor(Math.random() * 11) + 1) * 500; + +export const chartData = [ + { month: "January", revenue: generateRandomRevenue() }, + { month: "February", revenue: generateRandomRevenue() }, + { month: "March", revenue: generateRandomRevenue() }, + { month: "April", revenue: generateRandomRevenue() }, + { month: "May", revenue: generateRandomRevenue() }, + { month: "June", revenue: generateRandomRevenue() }, + { month: "July", revenue: generateRandomRevenue() }, + { month: "August", revenue: generateRandomRevenue() }, + { month: "September", revenue: generateRandomRevenue() }, + { month: "October", revenue: generateRandomRevenue() }, + { month: "November", revenue: generateRandomRevenue() }, + { month: "December", revenue: generateRandomRevenue() }, +]; + +export const chartConfig: ChartConfig = { + desktop: { + label: "Revenue", + color: "#F97316", + }, + mobile: { + label: "Mobile", + color: "#F97316", + }, +}; diff --git a/src/components/adminDashboard/productData.ts b/src/components/adminDashboard/productData.ts new file mode 100644 index 000000000..697e26b0f --- /dev/null +++ b/src/components/adminDashboard/productData.ts @@ -0,0 +1,37 @@ +const data = [ + { + name: "The Lemonade blender", + amount: "500 sales", + }, + { + name: "Bean Cake Powder", + amount: "250 sales", + }, + { + name: "Flour Mixer", + amount: "230 sales", + }, + { + name: "Blender", + amount: "500 sales", + }, + { + name: "A Food Product", + amount: "150 sales", + }, + { + name: "Cake Powder", + amount: "100 sales", + }, +]; + +const gradients = [ + " linear-gradient(180deg, #F6C790 0%, #E77F1E 100%)", + "linear-gradient(180deg, #D6F690 0%, #D7E71E 100%)", + "linear-gradient(180deg, #9290F6 0%, #461EE7 100%)", + " linear-gradient(180deg, #F690A8 0%, #E71E4E 100%)", + "linear-gradient(180deg, #B4F690 0%, #64E71E 100%)", + "linear-gradient(180deg, #E990F6 0%, #CB1EE7 100%)", +]; + +export { data, gradients }; diff --git a/src/test/adminDashboard/CardComponent.test.tsx b/src/test/adminDashboard/CardComponent.test.tsx new file mode 100644 index 000000000..3710a9519 --- /dev/null +++ b/src/test/adminDashboard/CardComponent.test.tsx @@ -0,0 +1,51 @@ +import { render, screen } from "@testing-library/react"; + +import CardComponent from "~/components/adminDashboard/CardComponent"; +import { cardData } from "~/components/adminDashboard/cardData"; + +describe("cardComponent", () => { + it("renders card with correct title, value, description, and icon", () => { + expect.assertions(5); + + const { title, value, description, icon } = cardData[0]; + + render( + , + ); + + expect(screen.getByText(title)).toBeInTheDocument(); + expect(screen.getByText(value)).toBeInTheDocument(); + expect(screen.getByText(description)).toBeInTheDocument(); + const img = screen.getByAltText(title); + expect(img).toBeInTheDocument(); + expect(img).toHaveAttribute("src", icon); + }); + + it("renders multiple cards correctly", () => { + expect.hasAssertions(); + + for (const { title, value, description, icon } of cardData) { + render( + , + ); + + expect(screen.getByText(title)).toBeInTheDocument(); + expect(screen.getByText(value)).toBeInTheDocument(); + const descriptions = screen.getAllByText(description); + expect(descriptions.length).toBeGreaterThan(0); + const img = screen.getByAltText(title); + expect(img).toBeInTheDocument(); + expect(img).toHaveAttribute("src", icon); + } + }); +}); diff --git a/src/test/adminDashboard/TopProductsComponent.test.tsx b/src/test/adminDashboard/TopProductsComponent.test.tsx new file mode 100644 index 000000000..72c22c55b --- /dev/null +++ b/src/test/adminDashboard/TopProductsComponent.test.tsx @@ -0,0 +1,53 @@ +import { render, screen } from "@testing-library/react"; + +import "@testing-library/jest-dom"; + +import { describe, expect, it } from "vitest"; + +import { data, gradients } from "../../components/adminDashboard/productData"; +import TopProductsComponent from "../../components/adminDashboard/TopProductsComponent"; + +describe("topProductsComponent", () => { + it("renders the list of products with correct data", () => { + expect.hasAssertions(); + render(); + const nameElements = screen.getAllByTestId(/^product-name-/); + expect(nameElements).toHaveLength(data.length); + const amountElements = screen.getAllByTestId(/^product-amount-/); + expect(amountElements).toHaveLength(data.length); + + for (const [index, { name, amount }] of data.entries()) { + expect(nameElements[index]).toHaveTextContent(name); + expect(amountElements[index]).toHaveTextContent(amount); + } + }); + + it("renders the component with title and description", () => { + expect.hasAssertions(); + render(); + expect(screen.getByText("Top Products")).toBeInTheDocument(); + expect( + screen.getByText("Your top selling products appear here."), + ).toBeInTheDocument(); + }); + + it("applies the correct gradient background to each product item", () => { + expect.hasAssertions(); + render(); + + const productItems = screen.getAllByRole("listitem"); + + for (const [index] of data.entries()) { + const productItem = productItems[index]; + const expectedGradient = gradients[index % gradients.length]; + + expect(productItem).toHaveStyle(`background: ${expectedGradient}`); + } + }); + + it('renders the "View All" button', () => { + expect.hasAssertions(); + render(); + expect(screen.getByText("View All")).toBeInTheDocument(); + }); +});