feat: implement complete invoicing application with CSV import and PDF export
- Add comprehensive CSV import system with drag-and-drop upload and validation - Create UniversalTable component with advanced filtering, searching, and batch actions - Implement invoice management (view, edit, delete) with professional PDF export - Add client management with full CRUD operations - Set up authentication with NextAuth.js and email/password login - Configure database schema with users, clients, invoices, and invoice_items tables - Build responsive UI with shadcn/ui components and emerald branding - Add type-safe API layer with tRPC and Zod validation - Include proper error handling and user feedback with toast notifications - Set up development environment with Bun, TypeScript, and Tailwind CSS
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
||||
import { clients } from "~/server/db/schema";
|
||||
|
||||
const createClientSchema = z.object({
|
||||
name: z.string().min(1, "Name is required"),
|
||||
email: z.string().email("Invalid email").optional(),
|
||||
phone: z.string().optional(),
|
||||
addressLine1: z.string().optional(),
|
||||
addressLine2: z.string().optional(),
|
||||
city: z.string().optional(),
|
||||
state: z.string().optional(),
|
||||
postalCode: z.string().optional(),
|
||||
country: z.string().optional(),
|
||||
});
|
||||
|
||||
const updateClientSchema = createClientSchema.partial().extend({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export const clientsRouter = createTRPCRouter({
|
||||
getAll: protectedProcedure.query(async ({ ctx }) => {
|
||||
return await ctx.db.query.clients.findMany({
|
||||
where: eq(clients.createdById, ctx.session.user.id),
|
||||
orderBy: (clients, { desc }) => [desc(clients.createdAt)],
|
||||
});
|
||||
}),
|
||||
|
||||
getById: protectedProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await ctx.db.query.clients.findFirst({
|
||||
where: eq(clients.id, input.id),
|
||||
with: {
|
||||
invoices: {
|
||||
orderBy: (invoices, { desc }) => [desc(invoices.createdAt)],
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(createClientSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return await ctx.db.insert(clients).values({
|
||||
...input,
|
||||
createdById: ctx.session.user.id,
|
||||
});
|
||||
}),
|
||||
|
||||
update: protectedProcedure
|
||||
.input(updateClientSchema)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const { id, ...data } = input;
|
||||
return await ctx.db
|
||||
.update(clients)
|
||||
.set({
|
||||
...data,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(clients.id, id));
|
||||
}),
|
||||
|
||||
delete: protectedProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
return await ctx.db.delete(clients).where(eq(clients.id, input.id));
|
||||
}),
|
||||
});
|
||||
Reference in New Issue
Block a user