Add fetch list of customer invoice
This commit is contained in:
parent
7b2ec83068
commit
46bbb9f8da
@ -1,8 +1,5 @@
|
||||
import { sql } from '@/bootstrap/db/db';
|
||||
import {
|
||||
CustomersTableType,
|
||||
InvoiceForm,
|
||||
InvoicesTable,
|
||||
Revenue,
|
||||
Invoice,
|
||||
Customer,
|
||||
@ -59,7 +56,7 @@ export async function fetchLatestInvoices() {
|
||||
|
||||
export async function fetchCardData() {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
connection()
|
||||
|
||||
try {
|
||||
// You can probably combine these into a single SQL query
|
||||
@ -97,127 +94,3 @@ export async function fetchCardData() {
|
||||
throw new Error('Failed to fetch card data.');
|
||||
}
|
||||
}
|
||||
|
||||
const ITEMS_PER_PAGE = 6;
|
||||
export async function fetchFilteredInvoices(
|
||||
query: string,
|
||||
currentPage: number,
|
||||
) {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
|
||||
const offset = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||
|
||||
try {
|
||||
const invoices = await sql`
|
||||
SELECT
|
||||
invoices.id,
|
||||
invoices.amount,
|
||||
invoices.date,
|
||||
invoices.status,
|
||||
customers.name,
|
||||
customers.email,
|
||||
customers.image_url
|
||||
FROM invoices
|
||||
JOIN customers ON invoices.customer_id = customers.id
|
||||
WHERE
|
||||
customers.name ILIKE ${`%${query}%`} OR
|
||||
customers.email ILIKE ${`%${query}%`} OR
|
||||
invoices.amount::text ILIKE ${`%${query}%`} OR
|
||||
invoices.date::text ILIKE ${`%${query}%`} OR
|
||||
invoices.status ILIKE ${`%${query}%`}
|
||||
ORDER BY invoices.date DESC
|
||||
LIMIT ${ITEMS_PER_PAGE} OFFSET ${offset}
|
||||
` as postgres.RowList<InvoicesTable[]>;
|
||||
|
||||
return invoices;
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch invoices.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchInvoicesPages(query: string) {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
try {
|
||||
const count = await sql`SELECT COUNT(*)
|
||||
FROM invoices
|
||||
JOIN customers ON invoices.customer_id = customers.id
|
||||
WHERE
|
||||
customers.name ILIKE ${`%${query}%`} OR
|
||||
customers.email ILIKE ${`%${query}%`} OR
|
||||
invoices.amount::text ILIKE ${`%${query}%`} OR
|
||||
invoices.date::text ILIKE ${`%${query}%`} OR
|
||||
invoices.status ILIKE ${`%${query}%`}
|
||||
`;
|
||||
|
||||
const totalPages = Math.ceil(Number(count.at(0)?.count) / ITEMS_PER_PAGE);
|
||||
return totalPages;
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch total number of invoices.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchInvoiceById(id: string) {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
try {
|
||||
const data = await sql`
|
||||
SELECT
|
||||
invoices.id,
|
||||
invoices.customer_id,
|
||||
invoices.amount,
|
||||
invoices.status
|
||||
FROM invoices
|
||||
WHERE invoices.id = ${id};
|
||||
` as postgres.RowList<InvoiceForm[]>;
|
||||
|
||||
const invoice = data.map((invoice) => ({
|
||||
...invoice,
|
||||
// Convert amount from cents to dollars
|
||||
amount: invoice.amount / 100,
|
||||
}));
|
||||
|
||||
return invoice[0];
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch invoice.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchFilteredCustomers(query: string) {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
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
|
||||
` as postgres.RowList<CustomersTableType[]>;
|
||||
|
||||
const customers = data.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.');
|
||||
}
|
||||
}
|
||||
|
@ -26,42 +26,6 @@ export type LatestInvoice = {
|
||||
amount: string;
|
||||
};
|
||||
|
||||
// The database returns a number for amount, but we later format it to a string with the formatCurrency function
|
||||
export type LatestInvoiceRaw = Omit<LatestInvoice, 'amount'> & {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type InvoicesTable = {
|
||||
id: string;
|
||||
customer_id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image_url: string;
|
||||
date: string;
|
||||
amount: number;
|
||||
status: 'pending' | 'paid';
|
||||
};
|
||||
|
||||
export type CustomersTableType = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image_url: string;
|
||||
total_invoices: number;
|
||||
total_pending: number;
|
||||
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 InvoiceForm = {
|
||||
id: string;
|
||||
customer_id: string;
|
||||
|
16
src/feature/customer-invoice/data/module/customer-di.ts
Normal file
16
src/feature/customer-invoice/data/module/customer-di.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import di from "@/bootstrap/di/init-di";
|
||||
import CustomerDbRepo from "@/feature/customer/data/repo/customer-db-repo";
|
||||
import { customerRepoKey } from "@/feature/customer/domain/i-repo/customer-repo";
|
||||
import fetchCustomersUsecase from "@/feature/customer/domain/usecase/fetch-customers-usecase";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
|
||||
export default function getCustomerDi(): DependencyContainer {
|
||||
const customerDi = di.createChildContainer()
|
||||
|
||||
customerDi.register(fetchCustomersUsecase.name, {
|
||||
useValue: fetchCustomersUsecase
|
||||
})
|
||||
|
||||
customerDi.register(customerRepoKey, CustomerDbRepo)
|
||||
return customerDi
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { formatCurrency } from "@/app/lib/utils";
|
||||
import { sql } from "@/bootstrap/db/db";
|
||||
import CustomerInvoice from "@/feature/customer-invoice/domain/entity/customer-invoice";
|
||||
import CustomerInvoiceRepo from "@/feature/customer-invoice/domain/i-repo/customer-invoice-repo";
|
||||
import { connection } from "next/server";
|
||||
import postgres from "postgres";
|
||||
|
||||
type customerInvoiceDbResponse = {
|
||||
id: string;
|
||||
name: string;
|
||||
image_url: string;
|
||||
email: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
export default class CustomerDbRepo implements CustomerInvoiceRepo {
|
||||
async fetchList(): Promise<CustomerInvoice[]> {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
|
||||
try {
|
||||
const data = await sql`
|
||||
SELECT invoices.amount, customers.name, customers.image_url, customers.email, invoices.id
|
||||
FROM invoices
|
||||
JOIN customers ON invoices.customer_id = customers.id
|
||||
ORDER BY invoices.date DESC
|
||||
LIMIT 5` as postgres.RowList<customerInvoiceDbResponse[]>;
|
||||
|
||||
return this.customerInvoicesDto(data)
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch the latest invoices.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private customerInvoicesDto(dbCustomers: customerInvoiceDbResponse[]): CustomerInvoice[] {
|
||||
return dbCustomers.map((customer) => this.customerInvoiceDto(customer));
|
||||
}
|
||||
|
||||
private customerInvoiceDto(dbCustomer: customerInvoiceDbResponse): CustomerInvoice {
|
||||
return new CustomerInvoice({
|
||||
id: dbCustomer.id,
|
||||
customerName: dbCustomer.name,
|
||||
customerEmail: dbCustomer.email,
|
||||
customerImageUrl: dbCustomer.image_url,
|
||||
invoicesAmount: formatCurrency(+dbCustomer.amount),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
export default class CustomerInvoice {
|
||||
id: string;
|
||||
customerName: string;
|
||||
customerImageUrl: string;
|
||||
customerEmail: string;
|
||||
invoicesAmount: string;
|
||||
|
||||
constructor({
|
||||
id,
|
||||
customerEmail,
|
||||
customerImageUrl,
|
||||
customerName,
|
||||
invoicesAmount
|
||||
}: CustomerInvoice) {
|
||||
this.id = id;
|
||||
this.customerEmail = customerEmail
|
||||
this.customerImageUrl = customerImageUrl
|
||||
this.customerName = customerName
|
||||
this.invoicesAmount = invoicesAmount
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import CustomerInvoice from "@/feature/customer-invoice/domain/entity/customer-invoice"
|
||||
|
||||
export default interface CustomerInvoiceRepo {
|
||||
fetchList(): Promise<CustomerInvoice[]>
|
||||
}
|
||||
|
||||
export const customerInvoiceRepoKey = "customerInvoiceRepoKey"
|
@ -0,0 +1,11 @@
|
||||
"use server"
|
||||
import serverDi from "@/feature/common/server-di";
|
||||
import CustomerInvoice from "@/feature/customer-invoice/domain/entity/customer-invoice";
|
||||
import CustomerInvoiceRepo, { customerInvoiceRepoKey } from "@/feature/customer-invoice/domain/i-repo/customer-invoice-repo";
|
||||
import { customerInvoiceModuleKey } from "@/feature/customer-invoice/invoice-module-key";
|
||||
|
||||
export default function fetchCustomerInvoicesUsecase(): Promise<CustomerInvoice[]> {
|
||||
const repo = serverDi(customerInvoiceModuleKey).resolve<CustomerInvoiceRepo>(customerInvoiceRepoKey)
|
||||
|
||||
return repo.fetchList()
|
||||
}
|
1
src/feature/customer-invoice/invoice-module-key.ts
Normal file
1
src/feature/customer-invoice/invoice-module-key.ts
Normal file
@ -0,0 +1 @@
|
||||
export const customerInvoiceModuleKey = "customerInvoiceModuleKey"
|
@ -1,4 +1,6 @@
|
||||
import di from "@/bootstrap/di/init-di";
|
||||
import CustomerDbRepo from "@/feature/customer/data/repo/customer-db-repo";
|
||||
import { customerRepoKey } from "@/feature/customer/domain/i-repo/customer-repo";
|
||||
import fetchCustomersUsecase from "@/feature/customer/domain/usecase/fetch-customers-usecase";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
|
||||
@ -9,5 +11,6 @@ export default function getCustomerDi(): DependencyContainer {
|
||||
useValue: fetchCustomersUsecase
|
||||
})
|
||||
|
||||
customerDi.register(customerRepoKey, CustomerDbRepo)
|
||||
return customerDi
|
||||
}
|
66
src/feature/customer/data/repo/customer-db-repo.ts
Normal file
66
src/feature/customer/data/repo/customer-db-repo.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { formatCurrency } from "@/app/lib/utils";
|
||||
import { sql } from "@/bootstrap/db/db";
|
||||
import Customer from "@/feature/customer/domain/entity/customer";
|
||||
import CustomerRepo from "@/feature/customer/domain/i-repo/customer-repo";
|
||||
import { connection } from "next/server";
|
||||
import postgres from "postgres";
|
||||
|
||||
type customerDbResponse = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image_url: string;
|
||||
total_invoices: string;
|
||||
total_pending: string;
|
||||
total_paid: string;
|
||||
}
|
||||
|
||||
export default class CustomerDbRepo implements CustomerRepo {
|
||||
async fetchList(query: string): Promise<Customer[]> {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
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
|
||||
` as postgres.RowList<customerDbResponse[]>;
|
||||
|
||||
|
||||
return this.customersDto(data);
|
||||
} catch (err) {
|
||||
console.error('Database Error:', err);
|
||||
throw new Error('Failed to fetch customer table.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private customersDto(dbCustomers: customerDbResponse[]): Customer[] {
|
||||
return dbCustomers.map((customer) => this.customerDto(customer));
|
||||
}
|
||||
|
||||
private customerDto(dbCustomer: customerDbResponse): Customer {
|
||||
return new Customer({
|
||||
id: dbCustomer.id,
|
||||
name: dbCustomer.name,
|
||||
email: dbCustomer.email,
|
||||
imageUrl: dbCustomer.image_url,
|
||||
totalInvoices: dbCustomer.total_invoices,
|
||||
totalPending: formatCurrency(Number(dbCustomer.total_pending)),
|
||||
totalPaid: formatCurrency(Number(dbCustomer.total_paid)),
|
||||
})
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +1,11 @@
|
||||
export type CustomersTableType = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image_url: string;
|
||||
total_invoices: number;
|
||||
total_pending: number;
|
||||
total_paid: number;
|
||||
};
|
||||
|
||||
export default class Customer {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
imageUrl: string;
|
||||
totalInvoices: number;
|
||||
totalPending: number;
|
||||
totalPaid: number;
|
||||
totalInvoices: string;
|
||||
totalPending: string;
|
||||
totalPaid: string;
|
||||
|
||||
constructor({
|
||||
id,
|
||||
|
@ -8,9 +8,9 @@ export default class CustomerFakeFactory {
|
||||
name: faker.person.fullName(),
|
||||
email: faker.internet.email(),
|
||||
imageUrl: faker.image.url(),
|
||||
totalInvoices: faker.number.int(),
|
||||
totalPaid: faker.number.int(),
|
||||
totalPending: faker.number.int(),
|
||||
totalInvoices: faker.number.int().toLocaleString(),
|
||||
totalPaid: faker.finance.amount(),
|
||||
totalPending: faker.number.int().toLocaleString(),
|
||||
})
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user