Skip to content

Commit 56da5e3

Browse files
committed
初始化上传项目
1 parent c0833eb commit 56da5e3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4387
-1
lines changed

.env.example

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copy from .env.local on the Vercel dashboard
2+
# https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database
3+
POSTGRES_URL=
4+
POSTGRES_PRISMA_URL=
5+
POSTGRES_URL_NON_POOLING=
6+
POSTGRES_USER=
7+
POSTGRES_HOST=
8+
POSTGRES_PASSWORD=
9+
POSTGRES_DATABASE=
10+
11+
# `openssl rand -base64 32`
12+
AUTH_SECRET=
13+
AUTH_URL=http://localhost:3000/api/auth

.gitignore

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env*.local
29+
.env
30+
31+
# vercel
32+
.vercel
33+
34+
# typescript
35+
*.tsbuildinfo
36+
next-env.d.ts

README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,5 @@
1-
# nextjs-dashboard
1+
## Next.js App Router Course - Starter
2+
3+
This is the starter template for the Next.js App Router Course. It contains the starting code for the dashboard application.
4+
5+
For more information, see the [course curriculum](https://nextjs.org/learn) on the Next.js Website.

app/dashboard/customers/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <p>Customers Page</p>;
3+
}

app/dashboard/invoices/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <p>Invoices Page</p>;
3+
}

app/dashboard/layout.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import SideNav from '@/app/ui/dashboard/sidenav';
2+
3+
export default function Layout({ children }: { children: React.ReactNode }) {
4+
return (
5+
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
6+
<div className="w-full flex-none md:w-64">
7+
<SideNav />
8+
</div>
9+
<div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
10+
</div>
11+
);
12+
}

app/dashboard/page.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default function Page() {
2+
return <p>Dashboard Page</p>;
3+
}

app/layout.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import '@/app/ui/global.css';
2+
import { inter } from '@/app/ui/fonts';
3+
4+
export default function RootLayout({
5+
children,
6+
}: {
7+
children: React.ReactNode;
8+
}) {
9+
return (
10+
<html lang="en">
11+
<body className={`${inter.className} antialiased`}>{children}</body>
12+
</html>
13+
);
14+
}

app/lib/data.ts

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
import { sql } from '@vercel/postgres';
2+
import {
3+
CustomerField,
4+
CustomersTableType,
5+
InvoiceForm,
6+
InvoicesTable,
7+
LatestInvoiceRaw,
8+
Revenue,
9+
} from './definitions';
10+
import { formatCurrency } from './utils';
11+
12+
export async function fetchRevenue() {
13+
try {
14+
// Artificially delay a response for demo purposes.
15+
// Don't do this in production :)
16+
17+
// console.log('Fetching revenue data...');
18+
// await new Promise((resolve) => setTimeout(resolve, 3000));
19+
20+
const data = await sql<Revenue>`SELECT * FROM revenue`;
21+
22+
// console.log('Data fetch completed after 3 seconds.');
23+
24+
return data.rows;
25+
} catch (error) {
26+
console.error('Database Error:', error);
27+
throw new Error('Failed to fetch revenue data.');
28+
}
29+
}
30+
31+
export async function fetchLatestInvoices() {
32+
try {
33+
const data = await sql<LatestInvoiceRaw>`
34+
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
35+
FROM invoices
36+
JOIN customers ON invoices.customer_id = customers.id
37+
ORDER BY invoices.date DESC
38+
LIMIT 5`;
39+
40+
const latestInvoices = data.rows.map((invoice) => ({
41+
...invoice,
42+
amount: formatCurrency(invoice.amount),
43+
}));
44+
return latestInvoices;
45+
} catch (error) {
46+
console.error('Database Error:', error);
47+
throw new Error('Failed to fetch the latest invoices.');
48+
}
49+
}
50+
51+
export async function fetchCardData() {
52+
try {
53+
// You can probably combine these into a single SQL query
54+
// However, we are intentionally splitting them to demonstrate
55+
// how to initialize multiple queries in parallel with JS.
56+
const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
57+
const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
58+
const invoiceStatusPromise = sql`SELECT
59+
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
60+
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS "pending"
61+
FROM invoices`;
62+
63+
const data = await Promise.all([
64+
invoiceCountPromise,
65+
customerCountPromise,
66+
invoiceStatusPromise,
67+
]);
68+
69+
const numberOfInvoices = Number(data[0].rows[0].count ?? '0');
70+
const numberOfCustomers = Number(data[1].rows[0].count ?? '0');
71+
const totalPaidInvoices = formatCurrency(data[2].rows[0].paid ?? '0');
72+
const totalPendingInvoices = formatCurrency(data[2].rows[0].pending ?? '0');
73+
74+
return {
75+
numberOfCustomers,
76+
numberOfInvoices,
77+
totalPaidInvoices,
78+
totalPendingInvoices,
79+
};
80+
} catch (error) {
81+
console.error('Database Error:', error);
82+
throw new Error('Failed to fetch card data.');
83+
}
84+
}
85+
86+
const ITEMS_PER_PAGE = 6;
87+
export async function fetchFilteredInvoices(
88+
query: string,
89+
currentPage: number,
90+
) {
91+
const offset = (currentPage - 1) * ITEMS_PER_PAGE;
92+
93+
try {
94+
const invoices = await sql<InvoicesTable>`
95+
SELECT
96+
invoices.id,
97+
invoices.amount,
98+
invoices.date,
99+
invoices.status,
100+
customers.name,
101+
customers.email,
102+
customers.image_url
103+
FROM invoices
104+
JOIN customers ON invoices.customer_id = customers.id
105+
WHERE
106+
customers.name ILIKE ${`%${query}%`} OR
107+
customers.email ILIKE ${`%${query}%`} OR
108+
invoices.amount::text ILIKE ${`%${query}%`} OR
109+
invoices.date::text ILIKE ${`%${query}%`} OR
110+
invoices.status ILIKE ${`%${query}%`}
111+
ORDER BY invoices.date DESC
112+
LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset}
113+
`;
114+
115+
return invoices.rows;
116+
} catch (error) {
117+
console.error('Database Error:', error);
118+
throw new Error('Failed to fetch invoices.');
119+
}
120+
}
121+
122+
export async function fetchInvoicesPages(query: string) {
123+
try {
124+
const count = await sql`SELECT COUNT(*)
125+
FROM invoices
126+
JOIN customers ON invoices.customer_id = customers.id
127+
WHERE
128+
customers.name ILIKE ${`%${query}%`} OR
129+
customers.email ILIKE ${`%${query}%`} OR
130+
invoices.amount::text ILIKE ${`%${query}%`} OR
131+
invoices.date::text ILIKE ${`%${query}%`} OR
132+
invoices.status ILIKE ${`%${query}%`}
133+
`;
134+
135+
const totalPages = Math.ceil(Number(count.rows[0].count) / ITEMS_PER_PAGE);
136+
return totalPages;
137+
} catch (error) {
138+
console.error('Database Error:', error);
139+
throw new Error('Failed to fetch total number of invoices.');
140+
}
141+
}
142+
143+
export async function fetchInvoiceById(id: string) {
144+
try {
145+
const data = await sql<InvoiceForm>`
146+
SELECT
147+
invoices.id,
148+
invoices.customer_id,
149+
invoices.amount,
150+
invoices.status
151+
FROM invoices
152+
WHERE invoices.id = ${id};
153+
`;
154+
155+
const invoice = data.rows.map((invoice) => ({
156+
...invoice,
157+
// Convert amount from cents to dollars
158+
amount: invoice.amount / 100,
159+
}));
160+
161+
return invoice[0];
162+
} catch (error) {
163+
console.error('Database Error:', error);
164+
throw new Error('Failed to fetch invoice.');
165+
}
166+
}
167+
168+
export async function fetchCustomers() {
169+
try {
170+
const data = await sql<CustomerField>`
171+
SELECT
172+
id,
173+
name
174+
FROM customers
175+
ORDER BY name ASC
176+
`;
177+
178+
const customers = data.rows;
179+
return customers;
180+
} catch (err) {
181+
console.error('Database Error:', err);
182+
throw new Error('Failed to fetch all customers.');
183+
}
184+
}
185+
186+
export async function fetchFilteredCustomers(query: string) {
187+
try {
188+
const data = await sql<CustomersTableType>`
189+
SELECT
190+
customers.id,
191+
customers.name,
192+
customers.email,
193+
customers.image_url,
194+
COUNT(invoices.id) AS total_invoices,
195+
SUM(CASE WHEN invoices.status = 'pending' THEN invoices.amount ELSE 0 END) AS total_pending,
196+
SUM(CASE WHEN invoices.status = 'paid' THEN invoices.amount ELSE 0 END) AS total_paid
197+
FROM customers
198+
LEFT JOIN invoices ON customers.id = invoices.customer_id
199+
WHERE
200+
customers.name ILIKE ${`%${query}%`} OR
201+
customers.email ILIKE ${`%${query}%`}
202+
GROUP BY customers.id, customers.name, customers.email, customers.image_url
203+
ORDER BY customers.name ASC
204+
`;
205+
206+
const customers = data.rows.map((customer) => ({
207+
...customer,
208+
total_pending: formatCurrency(customer.total_pending),
209+
total_paid: formatCurrency(customer.total_paid),
210+
}));
211+
212+
return customers;
213+
} catch (err) {
214+
console.error('Database Error:', err);
215+
throw new Error('Failed to fetch customer table.');
216+
}
217+
}

0 commit comments

Comments
 (0)