diff --git a/dashboard/15-final/app/dashboard/customers/page.tsx b/dashboard/15-final/app/dashboard/customers/page.tsx index 531ecce..c45ee90 100644 --- a/dashboard/15-final/app/dashboard/customers/page.tsx +++ b/dashboard/15-final/app/dashboard/customers/page.tsx @@ -1,9 +1,23 @@ +import { fetchFilteredCustomers } from '@/app/lib/data'; import CustomersTable from '@/app/ui/customers/table'; -export default function Page() { +export default async function Page({ + searchParams, +}: { + searchParams: + | { + query: string | undefined; + page: string | undefined; + } + | undefined; +}) { + const query = searchParams?.query || ''; + + const customers = await fetchFilteredCustomers(query); + return (
- +
); } diff --git a/dashboard/15-final/app/dashboard/invoices/page.tsx b/dashboard/15-final/app/dashboard/invoices/page.tsx index 7b19125..11bfce8 100644 --- a/dashboard/15-final/app/dashboard/invoices/page.tsx +++ b/dashboard/15-final/app/dashboard/invoices/page.tsx @@ -1,5 +1,5 @@ import Pagination from '@/app/ui/invoices/pagination'; -import Search from '@/app/ui/invoices/search'; +import Search from '@/app/ui/search'; import { CreateInvoice } from '@/app/ui/invoices/buttons'; import Table from '@/app/ui/invoices/table'; import { fetchFilteredInvoices } from '@/app/lib/data'; @@ -29,7 +29,7 @@ export default async function Page({

Invoices

- +
diff --git a/dashboard/15-final/app/lib/data.ts b/dashboard/15-final/app/lib/data.ts index 2ddced7..f6d291f 100644 --- a/dashboard/15-final/app/lib/data.ts +++ b/dashboard/15-final/app/lib/data.ts @@ -44,7 +44,7 @@ export async function fetchCounts() { export async function fetchTotalAmountByStatus() { try { - const data = await sql`SELECT + const data = await sql`SELECT SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid", SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending" FROM invoices`; @@ -61,8 +61,8 @@ export async function fetchTotalAmountByStatus() { export async function fetchLatestInvoices() { try { const data = await sql` - SELECT invoices.amount, customers.name, customers.image_url, customers.email - FROM invoices + SELECT invoices.amount, customers.name, customers.image_url, customers.email + FROM invoices JOIN customers ON invoices.customer_id = customers.id ORDER BY invoices.date DESC LIMIT 5`; @@ -92,7 +92,7 @@ export async function fetchFilteredInvoices( invoices.amount, invoices.date, invoices.status, - customers.name, + customers.name, customers.email, customers.image_url FROM invoices @@ -161,7 +161,7 @@ export async function fetchInvoiceById(id: string) { export async function fetchCustomerNames() { try { const data = await sql` - SELECT + SELECT id, name FROM customers @@ -179,17 +179,17 @@ export async function fetchCustomerNames() { export async function fetchCustomersTable() { try { const data = await sql` - SELECT + SELECT customers.id, customers.name, customers.email, customers.image_url, COUNT(invoices.id) AS total_invoices, - SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending, - SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid - FROM customers - LEFT JOIN invoices ON customers.id = invoices.customer_id - GROUP BY customers.id, customers.name, customers.email, customers.image_url + SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending, + SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid + FROM customers + LEFT JOIN invoices ON customers.id = invoices.customer_id + GROUP BY customers.id, customers.name, customers.email, customers.image_url ORDER BY customers.name ASC `; @@ -205,3 +205,36 @@ export async function fetchCustomersTable() { throw new Error('Failed to fetch customer table.'); } } + +export async function fetchFilteredCustomers(query: string) { + try { + const data = await sql` + SELECT + customers.id, + customers.name, + customers.email, + customers.image_url, + COUNT(invoices.id) AS total_invoices, + SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending, + SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid + FROM customers + LEFT JOIN invoices ON customers.id = invoices.customer_id + WHERE + customers.name ILIKE ${`%${query}%`} OR + customers.email ILIKE ${`%${query}%`} + GROUP BY customers.id, customers.name, customers.email, customers.image_url + ORDER BY customers.name ASC + `; + + const customers = data.rows.map((customer) => ({ + ...customer, + total_pending: formatCurrency(customer.total_pending), + total_paid: formatCurrency(customer.total_paid), + })); + + return customers; + } catch (err) { + console.error('Database Error:', err); + throw new Error('Failed to fetch customer table.'); + } +} diff --git a/dashboard/15-final/app/lib/definitions.ts b/dashboard/15-final/app/lib/definitions.ts index 8586ef4..0d609f2 100644 --- a/dashboard/15-final/app/lib/definitions.ts +++ b/dashboard/15-final/app/lib/definitions.ts @@ -65,6 +65,16 @@ export type CustomersTable = { total_paid: number; }; +export type FormattedCustomersTable = { + id: string; + name: string; + email: string; + image_url: string; + total_invoices: number; + total_pending: string; + total_paid: string; +}; + export type CustomerName = { id: string; name: string; diff --git a/dashboard/15-final/app/lib/dummy-data.js b/dashboard/15-final/app/lib/dummy-data.js index 7cfa132..9a2ee95 100644 --- a/dashboard/15-final/app/lib/dummy-data.js +++ b/dashboard/15-final/app/lib/dummy-data.js @@ -10,28 +10,52 @@ const users = [ const customers = [ { - id: '93980f8c-a5e4-484c-a469-2d12ca8fdde3', - name: 'Ada Lovelace', - email: 'ada@lovelace.com', - image_url: '/customers/ada-lovelace.png', + id: '3958dc9e-712f-4377-85e9-fec4b6a6442a', + name: 'Delba de Oliveira', + email: 'delba@oliveira.com', + image_url: '/customers/delba-de-oliveira.png', }, { - id: 'e53120f8-0301-437b-924a-0288f4ec6040', - name: 'Grace Hopper', - email: 'grace@hopper.com', - image_url: '/customers/grace-hopper.png', - }, - { - id: '030fab4c-18d7-4ed2-814c-4171cc67bca8', - name: 'Hedy Lammar', - email: 'hedy@lammar.com', - image_url: '/customers/hedy-lammar.png', + id: '3958dc9e-742f-4377-85e9-fec4b6a6442a', + name: 'Lee Robinson', + email: 'lee@robinson.com', + image_url: '/customers/lee-robinson.png', }, { id: '3958dc9e-737f-4377-85e9-fec4b6a6442a', - name: 'Margaret Hamilton', - email: 'margaret@hamilton.com', - image_url: '/customers/margaret-hamilton.png', + name: 'Guillermo Rauch', + email: 'guillermo@rauch.com', + image_url: '/customers/guillermo-rauch.png', + }, + { + id: '50ca3e18-62cd-11ee-8c99-0242ac120002', + name: 'Jared Palmer', + email: 'jared@palmer.com', + image_url: '/customers/jared-palmer.png', + }, + { + id: '3958dc9e-787f-4377-85e9-fec4b6a6442a', + name: 'Steph Dietz', + email: 'steph@dietz.com', + image_url: '/customers/steph-dietz.png', + }, + { + id: '76d65c26-f784-44a2-ac19-586678f7c2f2', + name: 'Tom Occhino', + email: 'tom@occhino.com', + image_url: '/customers/tom-occhino.png', + }, + { + id: 'd6e15727-9fe1-4961-8c5b-ea44a9bd81aa', + name: 'Evil Rabbit', + email: 'evil@rabbit.com', + image_url: '/customers/evil-rabbit.png', + }, + { + id: '126eed9c-c90c-4ef6-a4a8-fcf7408d3c66', + name: 'Emil Kowalski', + email: 'emil@kowalski.com', + image_url: '/customers/emil-kowalski.png', }, ]; @@ -49,7 +73,7 @@ const invoices = [ date: '2022-11-14', }, { - customer_id: customers[2].id, + customer_id: customers[4].id, amount: 3040, status: 'paid', date: '2022-10-29', @@ -61,19 +85,19 @@ const invoices = [ date: '2023-09-10', }, { - customer_id: customers[0].id, + customer_id: customers[5].id, amount: 34577, status: 'pending', date: '2023-08-05', }, { - customer_id: customers[1].id, + customer_id: customers[7].id, amount: 54246, status: 'pending', date: '2023-07-16', }, { - customer_id: customers[2].id, + customer_id: customers[6].id, amount: 8945, status: 'pending', date: '2023-06-27', @@ -85,13 +109,13 @@ const invoices = [ date: '2023-06-09', }, { - customer_id: customers[2].id, + customer_id: customers[4].id, amount: 1250, status: 'paid', date: '2023-06-17', }, { - customer_id: customers[0].id, + customer_id: customers[5].id, amount: 8945, status: 'paid', date: '2023-06-07', @@ -103,7 +127,7 @@ const invoices = [ date: '2023-08-19', }, { - customer_id: customers[2].id, + customer_id: customers[5].id, amount: 8945, status: 'paid', date: '2023-06-03', @@ -115,7 +139,7 @@ const invoices = [ date: '2023-06-18', }, { - customer_id: customers[3].id, + customer_id: customers[0].id, amount: 8945, status: 'paid', date: '2023-10-04', diff --git a/dashboard/15-final/app/ui/button.tsx b/dashboard/15-final/app/ui/button.tsx index f65aa1d..ff6253b 100644 --- a/dashboard/15-final/app/ui/button.tsx +++ b/dashboard/15-final/app/ui/button.tsx @@ -9,7 +9,7 @@ export function Button({ children, className, ...rest }: ButtonProps) {
- +
+
+ {customers?.map((customer) => ( +
+
+
+
+
+ +

{customer.name}

+
+
+

+ {customer.email} +

+
+
+
+
+

Pending

+

{customer.total_pending}

+
+
+

Paid

+

{customer.total_paid}

+
+
+
+

{customer.total_invoices} invoices

+
+
+ ))} +
+
+ - - - - - - + {customers.map((customer) => ( - - + - - - - diff --git a/dashboard/15-final/app/ui/invoices/buttons.tsx b/dashboard/15-final/app/ui/invoices/buttons.tsx index f3e348b..eed4d4f 100644 --- a/dashboard/15-final/app/ui/invoices/buttons.tsx +++ b/dashboard/15-final/app/ui/invoices/buttons.tsx @@ -6,7 +6,7 @@ export function CreateInvoice() { return ( Create Invoice{' '} @@ -18,7 +18,7 @@ export function UpdateInvoice({ id }: { id: string }) { return ( @@ -29,7 +29,7 @@ export function DeleteInvoice({ id }: { id: string }) { return (
- diff --git a/dashboard/15-final/app/ui/invoices/pagination.tsx b/dashboard/15-final/app/ui/invoices/pagination.tsx index 71f6f39..044a854 100644 --- a/dashboard/15-final/app/ui/invoices/pagination.tsx +++ b/dashboard/15-final/app/ui/invoices/pagination.tsx @@ -16,8 +16,8 @@ export default function Pagination({ const searchParams = useSearchParams(); const allPages = Array.from({ length: totalPages }, (_, i) => i + 1); - const PreviousPageTag = currentPage === 1 ? 'p' : Link; - const NextPageTag = currentPage === totalPages ? 'p' : Link; + const PreviousPageTag = Link; + const NextPageTag = Link; const createPageUrl = (pageNumber: number) => { const params = new URLSearchParams(searchParams); @@ -30,9 +30,10 @@ export default function Pagination({ @@ -48,7 +49,7 @@ export default function Pagination({ className={clsx( i === 0 && 'rounded-l-md', i === allPages.length - 1 && 'rounded-r-md', - 'flex h-10 w-10 items-center justify-center text-sm ring-1 ring-inset ring-gray-300', + 'flex h-10 w-10 items-center justify-center text-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-100', { 'z-10 bg-blue-600 text-white ring-blue-600': currentPage === page, @@ -63,7 +64,7 @@ export default function Pagination({ Pending - + ) : null} {status === 'paid' ? ( <> Paid - + ) : null} diff --git a/dashboard/15-final/app/ui/invoices/table.tsx b/dashboard/15-final/app/ui/invoices/table.tsx index 94fb312..891c2c7 100644 --- a/dashboard/15-final/app/ui/invoices/table.tsx +++ b/dashboard/15-final/app/ui/invoices/table.tsx @@ -27,11 +27,11 @@ export default async function InvoicesTable({ } `; return ( -
+
-
+
{invoices?.map((invoice) => (
+ Name + Email + Total Invoices + Total Pending + Total Paid
+
{customer.name}

+ {customer.email} + {customer.total_invoices} + {customer.total_pending} + {customer.total_paid}
- + - - - - - {invoices?.map((invoice) => ( - + - - - - - diff --git a/dashboard/15-final/app/ui/invoices/search.tsx b/dashboard/15-final/app/ui/search.tsx similarity index 55% rename from dashboard/15-final/app/ui/invoices/search.tsx rename to dashboard/15-final/app/ui/search.tsx index a7a3977..bb58994 100644 --- a/dashboard/15-final/app/ui/invoices/search.tsx +++ b/dashboard/15-final/app/ui/search.tsx @@ -4,7 +4,7 @@ import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useDebouncedCallback } from 'use-debounce'; -export default function Search() { +export default function Search({ placeholder }: { placeholder: string }) { const searchParams = useSearchParams(); const { replace } = useRouter(); const pathname = usePathname(); @@ -22,22 +22,19 @@ export default function Search() { }, 300); return ( -
+
-
- - { - handleSearch(e.target.value); - }} - defaultValue={searchParams.get('query')?.toString()} - /> -
+ { + handleSearch(e.target.value); + }} + defaultValue={searchParams.get('query')?.toString()} + /> +
); } diff --git a/dashboard/15-final/public/customers/ada-lovelace.png b/dashboard/15-final/public/customers/ada-lovelace.png deleted file mode 100644 index 962308e..0000000 Binary files a/dashboard/15-final/public/customers/ada-lovelace.png and /dev/null differ diff --git a/dashboard/15-final/public/customers/delba-de-oliveira.png b/dashboard/15-final/public/customers/delba-de-oliveira.png new file mode 100644 index 0000000..08db1b8 Binary files /dev/null and b/dashboard/15-final/public/customers/delba-de-oliveira.png differ diff --git a/dashboard/15-final/public/customers/emil-kowalski.png b/dashboard/15-final/public/customers/emil-kowalski.png new file mode 100644 index 0000000..da327a7 Binary files /dev/null and b/dashboard/15-final/public/customers/emil-kowalski.png differ diff --git a/dashboard/15-final/public/customers/evil-rabbit.png b/dashboard/15-final/public/customers/evil-rabbit.png new file mode 100644 index 0000000..fe7990f Binary files /dev/null and b/dashboard/15-final/public/customers/evil-rabbit.png differ diff --git a/dashboard/15-final/public/customers/grace-hopper.png b/dashboard/15-final/public/customers/grace-hopper.png deleted file mode 100644 index 4334b3c..0000000 Binary files a/dashboard/15-final/public/customers/grace-hopper.png and /dev/null differ diff --git a/dashboard/15-final/public/customers/guillermo-rauch.png b/dashboard/15-final/public/customers/guillermo-rauch.png new file mode 100644 index 0000000..e46c96f Binary files /dev/null and b/dashboard/15-final/public/customers/guillermo-rauch.png differ diff --git a/dashboard/15-final/public/customers/hedy-lammar.png b/dashboard/15-final/public/customers/hedy-lammar.png deleted file mode 100644 index 3a8fd6c..0000000 Binary files a/dashboard/15-final/public/customers/hedy-lammar.png and /dev/null differ diff --git a/dashboard/15-final/public/customers/jared-palmer.png b/dashboard/15-final/public/customers/jared-palmer.png new file mode 100644 index 0000000..e495518 Binary files /dev/null and b/dashboard/15-final/public/customers/jared-palmer.png differ diff --git a/dashboard/15-final/public/customers/lee-robinson.png b/dashboard/15-final/public/customers/lee-robinson.png new file mode 100644 index 0000000..633ae98 Binary files /dev/null and b/dashboard/15-final/public/customers/lee-robinson.png differ diff --git a/dashboard/15-final/public/customers/margaret-hamilton.png b/dashboard/15-final/public/customers/margaret-hamilton.png deleted file mode 100644 index 3abc439..0000000 Binary files a/dashboard/15-final/public/customers/margaret-hamilton.png and /dev/null differ diff --git a/dashboard/15-final/public/customers/steph-dietz.png b/dashboard/15-final/public/customers/steph-dietz.png new file mode 100644 index 0000000..e99f15a Binary files /dev/null and b/dashboard/15-final/public/customers/steph-dietz.png differ diff --git a/dashboard/15-final/public/customers/tom-occhino.png b/dashboard/15-final/public/customers/tom-occhino.png new file mode 100644 index 0000000..d1abbcd Binary files /dev/null and b/dashboard/15-final/public/customers/tom-occhino.png differ
+ Customer + Email + Amount + Date + Status
{invoice.name}

+ {invoice.email} + {formatCurrency(invoice.amount)} + {formatDateToLocal(invoice.date)} + +