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) => {
|
export const formatCurrency = (amount: number) => {
|
||||||
return (amount / 100).toLocaleString('en-US', {
|
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[]) => {
|
export const generateYAxis = (revenue: Revenue[]) => {
|
||||||
// Calculate what labels we need to display on the y-axis
|
// Calculate what labels we need to display on the y-axis
|
||||||
// based on highest record and in 1000s
|
// based on highest record and in 1000s
|
||||||
@ -34,36 +20,3 @@ export const generateYAxis = (revenue: Revenue[]) => {
|
|||||||
|
|
||||||
return { yAxisLabels, topLabel };
|
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 { DependencyContainer } from "tsyringe";
|
||||||
import { summaryInfoModuleKey } from "@/feature/core/summary-info/domain/summary-info-module-key";
|
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 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 {
|
export default function serverDi(module: string): DependencyContainer {
|
||||||
const getDi = {
|
const getDi = {
|
||||||
@ -17,6 +19,7 @@ export default function serverDi(module: string): DependencyContainer {
|
|||||||
[customerInvoiceModuleKey]: getCustomerInvoiceDi,
|
[customerInvoiceModuleKey]: getCustomerInvoiceDi,
|
||||||
[invoiceModuleKey]: getInvoiceDi,
|
[invoiceModuleKey]: getInvoiceDi,
|
||||||
[summaryInfoModuleKey]: getSummaryInfoDi,
|
[summaryInfoModuleKey]: getSummaryInfoDi,
|
||||||
|
[revenueModuleKey]: getRevenueDi,
|
||||||
}[module]
|
}[module]
|
||||||
|
|
||||||
if (!getDi) throw new Error("Server Di didn't found for module: " + 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