2d217fab47
- 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
70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
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));
|
|
}),
|
|
});
|