Add create random invoice module
This commit is contained in:
parent
96e7811fff
commit
021511e60e
@ -15,6 +15,6 @@
|
||||
"utils": "@/bootstrap/helpers/lib/ui-utils",
|
||||
"ui": "@/app/components",
|
||||
"lib": "@/bootstrap/helpers/lib",
|
||||
"hooks": "@/bootstrap/helpers/vm/hooks"
|
||||
"hooks": "@/bootstrap/helpers/hooks"
|
||||
}
|
||||
}
|
@ -48,9 +48,9 @@ export default async function LatestInvoices() {
|
||||
<h2 className="mb-4 text-xl md:text-2xl">
|
||||
Latest Invoices
|
||||
</h2>
|
||||
<div className="flex grow flex-col justify-between rounded-xl bg-gray-50 p-4">
|
||||
<div className="flex grow flex-col max-h-[66.5vh] justify-between rounded-xl bg-gray-50 p-4">
|
||||
|
||||
<div className="bg-white px-6">
|
||||
<div className="bg-white px-6 h-full overflow-y-auto">
|
||||
{invoices}
|
||||
</div>
|
||||
<div className="flex items-end mt-auto pb-2 pt-6">
|
||||
|
@ -1,9 +1,13 @@
|
||||
import CreateRandomInvoiceButtonVM from "@/app/dashboard/vm/create-random-invoice-button-vm";
|
||||
import di from "@/bootstrap/di/init-di"
|
||||
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase";
|
||||
|
||||
export default function dashboardAppModule() {
|
||||
const dashboardDi = di.createChildContainer()
|
||||
|
||||
dashboardDi.register(createInvoiceUsecase.name, {
|
||||
useValue: createInvoiceUsecase
|
||||
})
|
||||
dashboardDi.register(CreateRandomInvoiceButtonVM, CreateRandomInvoiceButtonVM)
|
||||
return dashboardDi
|
||||
}
|
||||
|
@ -1,15 +1,36 @@
|
||||
import ButtonVm from "@/app/components/button/button-vm";
|
||||
import useThrottle from "@/bootstrap/helpers/hooks/use-throttle";
|
||||
import BaseVM from "@/bootstrap/helpers/vm/base-vm";
|
||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase";
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
export default class CreateRandomInvoiceButtonVM extends BaseVM<ButtonVm> {
|
||||
private createInvoice: typeof createInvoiceUsecase
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.createInvoice = this.di.resolve(createInvoiceUsecase.name)
|
||||
}
|
||||
|
||||
useVM(): ButtonVm {
|
||||
const throttledOnClick = useThrottle(this.onClickHandler.bind(this), 5000)
|
||||
return {
|
||||
props: {
|
||||
title: "Button Title"
|
||||
title: "Create Random Invoice"
|
||||
},
|
||||
onClick: () => {
|
||||
console.log('clicked');
|
||||
}
|
||||
onClick: throttledOnClick
|
||||
}
|
||||
}
|
||||
|
||||
onClickHandler() {
|
||||
const fakedParams: InvoiceParam = {
|
||||
amount: faker.number.int({
|
||||
min: 1,
|
||||
max: 10
|
||||
}),
|
||||
status: "paid"
|
||||
}
|
||||
this.createInvoice(fakedParams)
|
||||
}
|
||||
}
|
18
src/bootstrap/helpers/hooks/use-throttle.ts
Normal file
18
src/bootstrap/helpers/hooks/use-throttle.ts
Normal file
@ -0,0 +1,18 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
/**
|
||||
*
|
||||
* @param callback
|
||||
* @param time In miliseconds
|
||||
*/
|
||||
export default function useThrottle<T extends Function>(callback: T, time: number = 2000) {
|
||||
const lastRun = useRef(Date.now())
|
||||
|
||||
return function() {
|
||||
if (Date.now() - lastRun.current <= time) return;
|
||||
lastRun.current = Date.now()
|
||||
return callback()
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ export default class CustomerInvoiceDbRepo implements CustomerInvoiceRepo {
|
||||
FROM invoices
|
||||
JOIN customers ON invoices.customer_id = customers.id
|
||||
ORDER BY invoices.date DESC
|
||||
LIMIT 5` as postgres.RowList<customerInvoiceDbResponse[]>;
|
||||
LIMIT 20 ` as postgres.RowList<customerInvoiceDbResponse[]>;
|
||||
|
||||
return this.customerInvoicesDto(data)
|
||||
} catch (error) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { sql } from "@/bootstrap/db/db";
|
||||
import { formatCurrency } from "@/feature/common/feature-helpers";
|
||||
import InvoiceRepo from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status";
|
||||
import postgres from "postgres";
|
||||
|
||||
@ -12,6 +13,28 @@ export default class InvoiceDbRepo implements InvoiceRepo {
|
||||
return data.count ?? 0
|
||||
}
|
||||
|
||||
async createInvoice(params: InvoiceParam): Promise<string> {
|
||||
const firstCustomerIdDb = await sql`SELECT
|
||||
id FROM customers
|
||||
ORDER BY id ASC
|
||||
LIMIT 1
|
||||
`
|
||||
const customerId = firstCustomerIdDb.at(0)?.id
|
||||
if (!customerId) throw new Error("There is no customer")
|
||||
|
||||
const { amount, status } = params;
|
||||
const amountInCents = amount * 100;
|
||||
const date = new Date().toISOString().split('T')[0];
|
||||
|
||||
// Insert data into the database
|
||||
const result = await sql`
|
||||
INSERT INTO invoices (customer_id, amount, status, date)
|
||||
VALUES (${customerId}, ${amountInCents}, ${status}, ${date})
|
||||
RETURNING id
|
||||
`;
|
||||
return result.at(0)?.id ?? ""
|
||||
}
|
||||
|
||||
async fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary> {
|
||||
const invoiceStatusPromise = await sql`SELECT
|
||||
SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid",
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { InvoiceParam } from "@/feature/core/invoice/domain/param/invoice-param"
|
||||
import InvoiceStatusSummary from "@/feature/core/invoice/domain/value-object/invoice-status"
|
||||
|
||||
export default interface InvoiceRepo {
|
||||
fetchAllInvoicesAmount(): Promise<number>
|
||||
fetchInvoicesStatusSummary(): Promise<InvoiceStatusSummary>
|
||||
createInvoice(params: InvoiceParam): Promise<string>
|
||||
}
|
||||
|
||||
export const invoiceRepoKey = "invoiceRepoKey"
|
10
src/feature/core/invoice/domain/param/invoice-param.ts
Normal file
10
src/feature/core/invoice/domain/param/invoice-param.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const invoiceSchema = z.object({
|
||||
amount: z.coerce.number().gt(0, { message: 'Please enter an amount greater than $0.' }),
|
||||
status: z.enum(['pending', 'paid'], {
|
||||
invalid_type_error: 'Please select an invoice status.',
|
||||
}),
|
||||
});
|
||||
|
||||
export type InvoiceParam = z.infer<typeof invoiceSchema>
|
@ -0,0 +1,22 @@
|
||||
"use server"
|
||||
import serverDi from "@/feature/common/server-di";
|
||||
import InvoiceRepo, { invoiceRepoKey } from "@/feature/core/invoice/domain/i-repo/invoice-repo";
|
||||
import { InvoiceParam, invoiceSchema } from "@/feature/core/invoice/domain/param/invoice-param";
|
||||
import { invoiceModuleKey } from "@/feature/core/invoice/invoice-module-key";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function createInvoiceUsecase(params: InvoiceParam): Promise<string | {errorMessage: string}> {
|
||||
const isParamsValid = invoiceSchema.safeParse(params)
|
||||
|
||||
if (!isParamsValid) {
|
||||
return {
|
||||
errorMessage: "Please pass correct params"
|
||||
}
|
||||
}
|
||||
const repo = serverDi(invoiceModuleKey).resolve<InvoiceRepo>(invoiceRepoKey)
|
||||
|
||||
revalidatePath("/dashboard")
|
||||
return repo.createInvoice(params)
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user