diff --git a/dashboard/15-final/app/dashboard/layout.tsx b/dashboard/15-final/app/dashboard/layout.tsx
index 97baef4..0eb56e6 100644
--- a/dashboard/15-final/app/dashboard/layout.tsx
+++ b/dashboard/15-final/app/dashboard/layout.tsx
@@ -5,9 +5,9 @@ export default function Layout({ children }: { children: React.ReactNode }) {
return (
-
+
-
{children}
+
{children}
);
diff --git a/dashboard/15-final/app/lib/calculations.tsx b/dashboard/15-final/app/lib/calculations.tsx
index 091633c..34ab6dc 100644
--- a/dashboard/15-final/app/lib/calculations.tsx
+++ b/dashboard/15-final/app/lib/calculations.tsx
@@ -1,4 +1,4 @@
-import { Invoice } from "./definitions";
+import { Invoice, Revenue } from "./definitions";
export const calculateInvoices = (
invoices: Invoice[],
@@ -12,3 +12,28 @@ export const calculateInvoices = (
currency: "USD",
});
};
+
+// Once a database is connected, we can use SQL to query the database directly
+// This will be more efficient than querying all invoices and then filtering them
+// E.g. "SELECT * FROM invoices
+// ORDER BY date DESC
+// LIMIT 5;"
+export const findLatestInvoices = (invoices: Invoice[]) => {
+ return [...invoices]
+ .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
+ .slice(0, 5);
+};
+
+export const generateYAxis = (revenue: Revenue[]) => {
+ // Calculate what labels we need to display on the y-axis
+ // based on highest record and in 1000s
+ const yAxisLabels = [];
+ const highestRecord = Math.max(...revenue.map((month) => month.revenue));
+ const topLabel = Math.ceil(highestRecord / 1000) * 1000;
+
+ for (let i = topLabel; i >= 0; i -= 1000) {
+ yAxisLabels.push(`$${i / 1000}K`);
+ }
+
+ return { yAxisLabels, topLabel };
+};
diff --git a/dashboard/15-final/app/lib/definitions.tsx b/dashboard/15-final/app/lib/definitions.tsx
index 95c633a..0e4cb89 100644
--- a/dashboard/15-final/app/lib/definitions.tsx
+++ b/dashboard/15-final/app/lib/definitions.tsx
@@ -2,23 +2,29 @@
// These describe the shape of the data, and what data type each property should accept.
export type User = {
- id: number
- name: string
- email: string
- password: string
-}
+ id: number;
+ name: string;
+ email: string;
+ password: string;
+};
export type Customer = {
- id: number
- name: string
- email: string
-}
+ id: number;
+ name: string;
+ email: string;
+ imageUrl: string;
+};
export type Invoice = {
- id: number
- customerId: number
- amount: number
- date: string
- status: "pending" | "paid" // In TypeScript, this is called a string union type.
+ id: number;
+ customerId: number;
+ amount: number;
+ status: "pending" | "paid"; // In TypeScript, this is called a string union type.
// It means that the "status" property can only be one of the two strings.
-}
+ date: string;
+};
+
+export type Revenue = {
+ month: string;
+ revenue: number;
+};
diff --git a/dashboard/15-final/app/lib/dummy-data.tsx b/dashboard/15-final/app/lib/dummy-data.tsx
index bf83355..3351802 100644
--- a/dashboard/15-final/app/lib/dummy-data.tsx
+++ b/dashboard/15-final/app/lib/dummy-data.tsx
@@ -1,4 +1,4 @@
-import { User, Customer, Invoice } from "./definitions";
+import { User, Customer, Invoice, Revenue } from "./definitions";
// This file contains dummy data that you'll be replacing with real data in Chapter 7.
export const users: User[] = [
@@ -13,23 +13,27 @@ export const users: User[] = [
export const customers: Customer[] = [
{
id: 1,
- name: "Lee",
- email: "lee@nextmail.com",
+ name: "Ada Lovelace",
+ email: "ada@earlycomputing.com",
+ imageUrl: "/customers/ada-lovelace.png",
},
{
id: 2,
- name: "Michael",
- email: "michael@nextmail.com",
+ name: "Grace Hopper",
+ email: "grace@personalcomputers.com",
+ imageUrl: "/customers/grace-hopper.png",
},
{
id: 3,
- name: "Steph",
- email: "steph@nextmail.com",
+ name: "Hedy Lammar",
+ email: "hedy@wifi.com",
+ imageUrl: "/customers/hedy-lammar.png",
},
{
id: 4,
- name: "Delba",
- email: "delba@nextmail.com",
+ name: "Margaret Hamilton",
+ email: "margaret@nasa.com",
+ imageUrl: "/customers/margaret-hamilton.png",
},
];
@@ -38,28 +42,64 @@ export const invoices: Invoice[] = [
id: 1,
customerId: 1,
amount: 15795,
- date: "2021-01-01",
status: "pending",
+ date: "2023-12-01",
},
{
id: 2,
customerId: 2,
amount: 20348,
- date: "2021-02-01",
status: "pending",
+ date: "2023-11-01",
},
{
id: 3,
customerId: 3,
amount: 3040,
- date: "2021-03-01",
status: "paid",
+ date: "2023-10-01",
},
{
id: 4,
customerId: 4,
amount: 44800,
- date: "2021-04-01",
status: "paid",
+ date: "2023-09-01",
+ },
+ {
+ id: 5,
+ customerId: 1,
+ amount: 34577,
+ status: "pending",
+ date: "2023-08-01",
+ },
+ {
+ id: 6,
+ customerId: 2,
+ amount: 54246,
+ status: "pending",
+ date: "2023-07-01",
+ },
+ {
+ id: 7,
+ customerId: 3,
+ amount: 8945,
+ status: "paid",
+ date: "2023-06-01",
},
];
+
+export const revenue: Revenue[] = [
+ { month: "Jan", revenue: 2000 },
+ { month: "Feb", revenue: 1800 },
+ { month: "Mar", revenue: 2200 },
+ { month: "Apr", revenue: 2500 },
+ { month: "May", revenue: 2300 },
+ { month: "Jun", revenue: 3200 },
+ { month: "Jul", revenue: 3500 },
+ { month: "Aug", revenue: 3700 },
+ { month: "Sep", revenue: 2500 },
+ { month: "Oct", revenue: 2800 },
+ { month: "Nov", revenue: 3000 },
+ { month: "Dec", revenue: 4800 },
+];
diff --git a/dashboard/15-final/app/ui/card.tsx b/dashboard/15-final/app/ui/card.tsx
index bb43e8a..c3311b5 100644
--- a/dashboard/15-final/app/ui/card.tsx
+++ b/dashboard/15-final/app/ui/card.tsx
@@ -24,15 +24,17 @@ export default function Card({
const Icon = iconMap[type];
return (
-
-
+
+
{title}
-
{value}
-
+00% since last month
+ {Icon ? (
+
+ ) : null}
- {Icon ? (
-
- ) : null}
+
+ {value}
+
+
+00% since last month
);
}
diff --git a/dashboard/15-final/app/ui/dashboard-overview.tsx b/dashboard/15-final/app/ui/dashboard-overview.tsx
index 32f0361..9065ba9 100644
--- a/dashboard/15-final/app/ui/dashboard-overview.tsx
+++ b/dashboard/15-final/app/ui/dashboard-overview.tsx
@@ -1,6 +1,8 @@
import Card from "@/app/ui/card";
-import { invoices, customers } from "@/app/lib/dummy-data";
+import { invoices, customers, revenue } from "@/app/lib/dummy-data";
import { calculateInvoices } from "@/app/lib/calculations";
+import RevenueChart from "@/app/ui/revenue-chart";
+import LatestInvoices from "@/app/ui/latest-invoices";
export default function DashboardOverview() {
const totalPaidInvoices = calculateInvoices(invoices, "paid");
@@ -9,15 +11,21 @@ export default function DashboardOverview() {
const numberOfCustomers = customers.length;
return (
-
-
-
-
-
-
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
);
}
diff --git a/dashboard/15-final/app/ui/dashboard-sidenav.tsx b/dashboard/15-final/app/ui/dashboard-sidenav.tsx
index e8c7126..2b5838a 100644
--- a/dashboard/15-final/app/ui/dashboard-sidenav.tsx
+++ b/dashboard/15-final/app/ui/dashboard-sidenav.tsx
@@ -47,7 +47,7 @@ export default function SideNav() {
)}
>
-
{tab.name}
+
{tab.name}
);
})}
diff --git a/dashboard/15-final/app/ui/dashboard-topnav.tsx b/dashboard/15-final/app/ui/dashboard-topnav.tsx
index 3ad1349..d5f95eb 100644
--- a/dashboard/15-final/app/ui/dashboard-topnav.tsx
+++ b/dashboard/15-final/app/ui/dashboard-topnav.tsx
@@ -2,8 +2,8 @@ import Search from "./search";
export default function TopNav() {
return (
-
+
- )
+ );
}
diff --git a/dashboard/15-final/app/ui/latest-invoices.tsx b/dashboard/15-final/app/ui/latest-invoices.tsx
new file mode 100644
index 0000000..6a832f5
--- /dev/null
+++ b/dashboard/15-final/app/ui/latest-invoices.tsx
@@ -0,0 +1,52 @@
+// InvoiceList.tsx
+import { Customer, Invoice } from "@/app/lib/definitions";
+import { findLatestInvoices } from "@/app/lib/calculations";
+
+export default function LatestInvoices({
+ invoices,
+ customers,
+}: {
+ invoices: Invoice[];
+ customers: Customer[];
+}) {
+ const lastFiveInvoices = findLatestInvoices(invoices);
+
+ return (
+
+
Latest Invoices
+
+ {lastFiveInvoices.map((invoice) => {
+ const customer = customers.find(
+ (customer) => customer.id === invoice.customerId,
+ );
+ return (
+
+
+

+
+
{customer?.name}
+
+ {customer?.email}
+
+
+
+
+ +{" "}
+ {(invoice.amount / 100).toLocaleString("en-US", {
+ style: "currency",
+ currency: "USD",
+ })}
+
+
+ );
+ })}
+
+ );
+}
diff --git a/dashboard/15-final/app/ui/revenue-chart.tsx b/dashboard/15-final/app/ui/revenue-chart.tsx
new file mode 100644
index 0000000..f9b0b61
--- /dev/null
+++ b/dashboard/15-final/app/ui/revenue-chart.tsx
@@ -0,0 +1,49 @@
+import { Revenue } from "@/app/lib/definitions";
+import { generateYAxis } from "@/app/lib/calculations";
+
+// This component is representational only.
+// For data visualization UI, check out:
+// https://www.chartjs.org/
+// https://airbnb.io/visx/
+// https://www.tremor.so/
+export default function RevenueChart({ revenue }: { revenue: Revenue[] }) {
+ const chartHeight = 350;
+ const { yAxisLabels, topLabel } = generateYAxis(revenue);
+
+ if (!revenue || revenue.length === 0) {
+ return
No data available.
;
+ }
+
+ return (
+
+
Revenue
+
+ {/* y-axis */}
+
+ {yAxisLabels.map((label, index) => (
+
{label}
+ ))}
+
+
+ {revenue.map((month, index) => (
+
+ {/* bars */}
+
+ {/* x-axis */}
+
+ {month.month}
+
+
+ ))}
+
+
+ );
+}
diff --git a/dashboard/15-final/public/customers/ada-lovelace.png b/dashboard/15-final/public/customers/ada-lovelace.png
new file mode 100644
index 0000000..962308e
Binary files /dev/null and b/dashboard/15-final/public/customers/ada-lovelace.png differ
diff --git a/dashboard/15-final/public/customers/grace-hopper.png b/dashboard/15-final/public/customers/grace-hopper.png
new file mode 100644
index 0000000..4334b3c
Binary files /dev/null and b/dashboard/15-final/public/customers/grace-hopper.png differ
diff --git a/dashboard/15-final/public/customers/hedy-lammar.png b/dashboard/15-final/public/customers/hedy-lammar.png
new file mode 100644
index 0000000..3a8fd6c
Binary files /dev/null and b/dashboard/15-final/public/customers/hedy-lammar.png differ
diff --git a/dashboard/15-final/public/customers/margaret-hamilton.png b/dashboard/15-final/public/customers/margaret-hamilton.png
new file mode 100644
index 0000000..3abc439
Binary files /dev/null and b/dashboard/15-final/public/customers/margaret-hamilton.png differ
diff --git a/dashboard/15-final/tailwind.config.ts b/dashboard/15-final/tailwind.config.ts
index c7ead80..083a5b7 100644
--- a/dashboard/15-final/tailwind.config.ts
+++ b/dashboard/15-final/tailwind.config.ts
@@ -1,20 +1,23 @@
-import type { Config } from 'tailwindcss'
+import type { Config } from "tailwindcss";
const config: Config = {
content: [
- './pages/**/*.{js,ts,jsx,tsx,mdx}',
- './components/**/*.{js,ts,jsx,tsx,mdx}',
- './app/**/*.{js,ts,jsx,tsx,mdx}',
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
backgroundImage: {
- 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
- 'gradient-conic':
- 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
+ "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
+ "gradient-conic":
+ "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
+ },
+ gridTemplateColumns: {
+ "13": "repeat(13, minmax(0, 1fr))",
},
},
},
plugins: [],
-}
-export default config
+};
+export default config;