From 021511e60e8f0aefce9e95a22448bc8c5037b999 Mon Sep 17 00:00:00 2001 From: behnam Date: Fri, 1 Nov 2024 20:00:16 +0300 Subject: [PATCH] Add create random invoice module --- components.json | 2 +- .../latest-invoices/latest-invoices.tsx | 4 +-- .../dashboard/module/dashboard-app-module.ts | 4 +++ .../vm/create-random-invoice-button-vm.ts | 29 ++++++++++++++++--- src/bootstrap/helpers/hooks/use-throttle.ts | 18 ++++++++++++ .../data/repo/customer-invoice-db-repo.ts | 2 +- .../core/invoice/data/repo/invoice-db-repo.ts | 23 +++++++++++++++ .../invoice/domain/i-repo/invoice-repo.ts | 2 ++ .../invoice/domain/param/invoice-param.ts | 10 +++++++ .../domain/usecase/create-invoice-usecase.ts | 22 ++++++++++++++ 10 files changed, 108 insertions(+), 8 deletions(-) create mode 100644 src/bootstrap/helpers/hooks/use-throttle.ts create mode 100644 src/feature/core/invoice/domain/param/invoice-param.ts create mode 100644 src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts diff --git a/components.json b/components.json index a0c8a84..88a82c6 100644 --- a/components.json +++ b/components.json @@ -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" } } \ No newline at end of file diff --git a/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx b/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx index 09b140b..09396cd 100644 --- a/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx +++ b/src/app/dashboard/components/server/latest-invoices/latest-invoices.tsx @@ -48,9 +48,9 @@ export default async function LatestInvoices() {

Latest Invoices

-
+
-
+
{invoices}
diff --git a/src/app/dashboard/module/dashboard-app-module.ts b/src/app/dashboard/module/dashboard-app-module.ts index a3611c1..60721eb 100644 --- a/src/app/dashboard/module/dashboard-app-module.ts +++ b/src/app/dashboard/module/dashboard-app-module.ts @@ -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 } diff --git a/src/app/dashboard/vm/create-random-invoice-button-vm.ts b/src/app/dashboard/vm/create-random-invoice-button-vm.ts index 3bbf802..11a258e 100644 --- a/src/app/dashboard/vm/create-random-invoice-button-vm.ts +++ b/src/app/dashboard/vm/create-random-invoice-button-vm.ts @@ -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 { + 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) + } } \ No newline at end of file diff --git a/src/bootstrap/helpers/hooks/use-throttle.ts b/src/bootstrap/helpers/hooks/use-throttle.ts new file mode 100644 index 0000000..ff2344f --- /dev/null +++ b/src/bootstrap/helpers/hooks/use-throttle.ts @@ -0,0 +1,18 @@ +"use client" + +import { useEffect, useRef } from "react" + +/** + * + * @param callback + * @param time In miliseconds + */ +export default function useThrottle(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() + } +} \ No newline at end of file diff --git a/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts b/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts index ba6eff5..9c19809 100644 --- a/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts +++ b/src/feature/core/customer-invoice/data/repo/customer-invoice-db-repo.ts @@ -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; + LIMIT 20 ` as postgres.RowList; return this.customerInvoicesDto(data) } catch (error) { diff --git a/src/feature/core/invoice/data/repo/invoice-db-repo.ts b/src/feature/core/invoice/data/repo/invoice-db-repo.ts index 66c0998..fd2219c 100644 --- a/src/feature/core/invoice/data/repo/invoice-db-repo.ts +++ b/src/feature/core/invoice/data/repo/invoice-db-repo.ts @@ -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 { + 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 { const invoiceStatusPromise = await sql`SELECT SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS "paid", diff --git a/src/feature/core/invoice/domain/i-repo/invoice-repo.ts b/src/feature/core/invoice/domain/i-repo/invoice-repo.ts index 90bac8f..983ac3f 100644 --- a/src/feature/core/invoice/domain/i-repo/invoice-repo.ts +++ b/src/feature/core/invoice/domain/i-repo/invoice-repo.ts @@ -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 fetchInvoicesStatusSummary(): Promise + createInvoice(params: InvoiceParam): Promise } export const invoiceRepoKey = "invoiceRepoKey" \ No newline at end of file diff --git a/src/feature/core/invoice/domain/param/invoice-param.ts b/src/feature/core/invoice/domain/param/invoice-param.ts new file mode 100644 index 0000000..1dce62a --- /dev/null +++ b/src/feature/core/invoice/domain/param/invoice-param.ts @@ -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 \ No newline at end of file diff --git a/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts b/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts new file mode 100644 index 0000000..1b3ea6a --- /dev/null +++ b/src/feature/core/invoice/domain/usecase/create-invoice-usecase.ts @@ -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 { + const isParamsValid = invoiceSchema.safeParse(params) + + if (!isParamsValid) { + return { + errorMessage: "Please pass correct params" + } + } + const repo = serverDi(invoiceModuleKey).resolve(invoiceRepoKey) + + revalidatePath("/dashboard") + return repo.createInvoice(params) + +} \ No newline at end of file