feature/research-di #1
@ -1,73 +0,0 @@
|
||||
import { sql } from '@/bootstrap/db/db';
|
||||
import {
|
||||
Revenue,
|
||||
Invoice,
|
||||
Customer,
|
||||
} from './definitions';
|
||||
import { formatCurrency } from './utils';
|
||||
import postgres from 'postgres';
|
||||
import { connection } from 'next/server';
|
||||
|
||||
export async function fetchRevenue() {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
|
||||
try {
|
||||
// Artificially delay a response for demo purposes.
|
||||
// Don't do this in production :)
|
||||
|
||||
console.log('Fetching revenue data...');
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
const data = await sql`SELECT * FROM revenue`;
|
||||
|
||||
console.log('Data fetch completed after 3 seconds.');
|
||||
|
||||
return data as postgres.RowList<Revenue[]>;
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch revenue data.');
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchCardData() {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
|
||||
try {
|
||||
// You can probably combine these into a single SQL query
|
||||
// However, we are intentionally splitting them to demonstrate
|
||||
// how to initialize multiple queries in parallel with JS.
|
||||
const invoiceCountPromise = sql`SELECT COUNT(*) FROM invoices`;
|
||||
const customerCountPromise = sql`SELECT COUNT(*) FROM customers`;
|
||||
const invoiceStatusPromise = sql`SELECT
|
||||
id,
|
||||
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`;
|
||||
|
||||
const data = await Promise.all([
|
||||
invoiceCountPromise,
|
||||
customerCountPromise,
|
||||
invoiceStatusPromise,
|
||||
]);
|
||||
|
||||
const invoices = data[0] as postgres.RowList<Invoice[]>
|
||||
const customres = data[1] as postgres.RowList<Customer[]>
|
||||
const invoiceStatus = data[2] as postgres.RowList<({id: string; paid: string, pending: string})[]>
|
||||
const numberOfInvoices = Number(invoices.count ?? '0');
|
||||
const numberOfCustomers = Number(customres.count ?? '0');
|
||||
const totalPaidInvoices = formatCurrency(Number(invoiceStatus.at(0)?.paid ?? '0'));
|
||||
const totalPendingInvoices = formatCurrency(Number(invoiceStatus.at(0)?.pending ?? '0'));
|
||||
|
||||
return {
|
||||
numberOfCustomers,
|
||||
numberOfInvoices,
|
||||
totalPaidInvoices,
|
||||
totalPendingInvoices,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch card data.');
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
export type Customer = {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
image_url: string;
|
||||
};
|
||||
|
||||
export type Invoice = {
|
||||
id: string; // Will be created on the database
|
||||
customer_id: string;
|
||||
amount: number; // Stored in cents
|
||||
status: 'pending' | 'paid';
|
||||
date: string;
|
||||
};
|
||||
|
||||
export type Revenue = {
|
||||
month: string;
|
||||
revenue: number;
|
||||
};
|
||||
|
||||
export type InvoiceForm = {
|
||||
id: string;
|
||||
customer_id: string;
|
||||
amount: number;
|
||||
status: 'pending' | 'paid';
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { Revenue } from './definitions';
|
||||
import Revenue from "@/feature/core/revenue/domain/entity/revenue";
|
||||
|
||||
export const formatCurrency = (amount: number) => {
|
||||
return (amount / 100).toLocaleString('en-US', {
|
||||
@ -7,20 +7,6 @@ export const formatCurrency = (amount: number) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const formatDateToLocal = (
|
||||
dateStr: string,
|
||||
locale: string = 'en-US',
|
||||
) => {
|
||||
const date = new Date(dateStr);
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
};
|
||||
const formatter = new Intl.DateTimeFormat(locale, options);
|
||||
return formatter.format(date);
|
||||
};
|
||||
|
||||
export const generateYAxis = (revenue: Revenue[]) => {
|
||||
// Calculate what labels we need to display on the y-axis
|
||||
// based on highest record and in 1000s
|
||||
@ -34,36 +20,3 @@ export const generateYAxis = (revenue: Revenue[]) => {
|
||||
|
||||
return { yAxisLabels, topLabel };
|
||||
};
|
||||
|
||||
export const generatePagination = (currentPage: number, totalPages: number) => {
|
||||
// If the total number of pages is 7 or less,
|
||||
// display all pages without any ellipsis.
|
||||
if (totalPages <= 7) {
|
||||
return Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
// If the current page is among the first 3 pages,
|
||||
// show the first 3, an ellipsis, and the last 2 pages.
|
||||
if (currentPage <= 3) {
|
||||
return [1, 2, 3, '...', totalPages - 1, totalPages];
|
||||
}
|
||||
|
||||
// If the current page is among the last 3 pages,
|
||||
// show the first 2, an ellipsis, and the last 3 pages.
|
||||
if (currentPage >= totalPages - 2) {
|
||||
return [1, 2, '...', totalPages - 2, totalPages - 1, totalPages];
|
||||
}
|
||||
|
||||
// If the current page is somewhere in the middle,
|
||||
// show the first page, an ellipsis, the current page and its neighbors,
|
||||
// another ellipsis, and the last page.
|
||||
return [
|
||||
1,
|
||||
'...',
|
||||
currentPage - 1,
|
||||
currentPage,
|
||||
currentPage + 1,
|
||||
'...',
|
||||
totalPages,
|
||||
];
|
||||
};
|
||||
|
@ -9,6 +9,8 @@ import { invoiceModuleKey } from "@/feature/core/invoice/invoice-module-key";
|
||||
import { DependencyContainer } from "tsyringe";
|
||||
import { summaryInfoModuleKey } from "@/feature/core/summary-info/domain/summary-info-module-key";
|
||||
import getSummaryInfoDi from "@/feature/core/summary-info/data/module/summary-info-di";
|
||||
import { revenueModuleKey } from "@/feature/core/revenue/domain/revenue-module-key";
|
||||
import getRevenueDi from "@/feature/core/revenue/data/module/revenue-di";
|
||||
|
||||
export default function serverDi(module: string): DependencyContainer {
|
||||
const getDi = {
|
||||
@ -17,6 +19,7 @@ export default function serverDi(module: string): DependencyContainer {
|
||||
[customerInvoiceModuleKey]: getCustomerInvoiceDi,
|
||||
[invoiceModuleKey]: getInvoiceDi,
|
||||
[summaryInfoModuleKey]: getSummaryInfoDi,
|
||||
[revenueModuleKey]: getRevenueDi,
|
||||
}[module]
|
||||
|
||||
if (!getDi) throw new Error("Server Di didn't found for module: " + module)
|
||||
|
10
src/feature/core/revenue/data/module/revenue-di.ts
Normal file
10
src/feature/core/revenue/data/module/revenue-di.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import di from "@/bootstrap/di/init-di"
|
||||
import RevenueDbRepo from "@/feature/core/revenue/data/repo/revenue-db-repo"
|
||||
import { revenueRepoKey } from "@/feature/core/revenue/domain/i-repo/revenue-repo"
|
||||
|
||||
export default function getRevenueDi() {
|
||||
const revenueDi = di.createChildContainer()
|
||||
|
||||
revenueDi.register(revenueRepoKey, RevenueDbRepo)
|
||||
return revenueDi
|
||||
}
|
43
src/feature/core/revenue/data/repo/revenue-db-repo.ts
Normal file
43
src/feature/core/revenue/data/repo/revenue-db-repo.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { sql } from "@/bootstrap/db/db";
|
||||
import Revenue from "@/feature/core/revenue/domain/entity/revenue";
|
||||
import RevenueRepo from "@/feature/core/revenue/domain/i-repo/revenue-repo";
|
||||
import { connection } from "next/server";
|
||||
import postgres from "postgres";
|
||||
|
||||
export type RevenueDbResponse = {
|
||||
month: string;
|
||||
revenue: number;
|
||||
};
|
||||
export default class RevenueDbRepo implements RevenueRepo {
|
||||
async fetchRevenues(): Promise<Revenue[]> {
|
||||
// This is equivalent to in fetch(..., {cache: 'no-store'}).
|
||||
connection()
|
||||
try {
|
||||
// Artificially delay a response for demo purposes.
|
||||
// Don't do this in production :)
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
|
||||
const data = await sql`SELECT * FROM revenue` as postgres.RowList<RevenueDbResponse[]>;
|
||||
|
||||
console.log('Data fetch completed after 3 seconds.');
|
||||
|
||||
return this.revenuesDto(data);
|
||||
} catch (error) {
|
||||
console.error('Database Error:', error);
|
||||
throw new Error('Failed to fetch revenue data.');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private revenuesDto(dbResponse: RevenueDbResponse[]): Revenue[] {
|
||||
return dbResponse.map((dbRevenue) => this.revenueDto(dbRevenue))
|
||||
}
|
||||
|
||||
private revenueDto(dbResponse: RevenueDbResponse): Revenue {
|
||||
return new Revenue({
|
||||
month: dbResponse.month,
|
||||
revenue: dbResponse.revenue
|
||||
})
|
||||
}
|
||||
|
||||
}
|
14
src/feature/core/revenue/domain/entity/revenue.ts
Normal file
14
src/feature/core/revenue/domain/entity/revenue.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export default class Revenue {
|
||||
month: string;
|
||||
revenue: number;
|
||||
|
||||
constructor(
|
||||
{
|
||||
month,
|
||||
revenue
|
||||
}: Revenue
|
||||
) {
|
||||
this.month = month
|
||||
this.revenue = revenue
|
||||
}
|
||||
}
|
7
src/feature/core/revenue/domain/i-repo/revenue-repo.ts
Normal file
7
src/feature/core/revenue/domain/i-repo/revenue-repo.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import Revenue from "@/feature/core/revenue/domain/entity/revenue";
|
||||
|
||||
export default interface RevenueRepo {
|
||||
fetchRevenues(): Promise<Revenue[]>
|
||||
}
|
||||
|
||||
export const revenueRepoKey = "revenueRepoKey"
|
1
src/feature/core/revenue/domain/revenue-module-key.ts
Normal file
1
src/feature/core/revenue/domain/revenue-module-key.ts
Normal file
@ -0,0 +1 @@
|
||||
export const revenueModuleKey = "RevenueModuleKey"
|
@ -0,0 +1,9 @@
|
||||
import serverDi from "@/feature/common/server-di";
|
||||
import Revenue from "@/feature/core/revenue/domain/entity/revenue";
|
||||
import RevenueRepo from "@/feature/core/revenue/domain/i-repo/revenue-repo";
|
||||
import { revenueModuleKey } from "@/feature/core/revenue/domain/revenue-module-key";
|
||||
|
||||
export default function fetchRevenuesUsecase(): Promise<Revenue[]> {
|
||||
const repo = serverDi(revenueModuleKey).resolve<RevenueRepo>(revenueModuleKey)
|
||||
return repo.fetchRevenues()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user