0e46fdafb2
- Implemented `AdministrationContent` component for managing account roles. - Created `AdministrationPage` to serve as the main entry point for administration tasks. - Added PDF preview functionality with `PdfPreviewFrame` component for invoice generation. - Introduced `InputColor` component for advanced color selection with various formats. - Established color conversion utilities in `color-converter.ts` for handling color formats. - Defined appearance-related schemas and types in `appearance.ts` for consistent theme management.
127 lines
3.6 KiB
TypeScript
127 lines
3.6 KiB
TypeScript
import { z } from "zod";
|
|
import { eq, and } from "drizzle-orm";
|
|
import { createTRPCRouter, protectedProcedure } from "../trpc";
|
|
import { invoiceTemplates } from "~/server/db/schema";
|
|
import { TRPCError } from "@trpc/server";
|
|
|
|
const createTemplateSchema = z.object({
|
|
name: z.string().min(1, "Name is required").max(255),
|
|
type: z.enum(["notes", "terms"]).default("notes"),
|
|
content: z.string().min(1, "Content is required"),
|
|
isDefault: z.boolean().default(false),
|
|
});
|
|
|
|
const updateTemplateSchema = createTemplateSchema.partial().extend({
|
|
id: z.string(),
|
|
});
|
|
|
|
export const invoiceTemplatesRouter = createTRPCRouter({
|
|
getAll: protectedProcedure.query(async ({ ctx }) => {
|
|
return await ctx.db.query.invoiceTemplates.findMany({
|
|
where: eq(invoiceTemplates.createdById, ctx.session.user.id),
|
|
orderBy: (t, { asc }) => [asc(t.type), asc(t.name)],
|
|
});
|
|
}),
|
|
|
|
getByType: protectedProcedure
|
|
.input(z.object({ type: z.enum(["notes", "terms"]) }))
|
|
.query(async ({ ctx, input }) => {
|
|
return await ctx.db.query.invoiceTemplates.findMany({
|
|
where: and(
|
|
eq(invoiceTemplates.createdById, ctx.session.user.id),
|
|
eq(invoiceTemplates.type, input.type),
|
|
),
|
|
orderBy: (t, { asc }) => [asc(t.name)],
|
|
});
|
|
}),
|
|
|
|
create: protectedProcedure
|
|
.input(createTemplateSchema)
|
|
.mutation(async ({ ctx, input }) => {
|
|
// If setting as default, unset others of same type
|
|
if (input.isDefault) {
|
|
await ctx.db
|
|
.update(invoiceTemplates)
|
|
.set({ isDefault: false })
|
|
.where(
|
|
and(
|
|
eq(invoiceTemplates.createdById, ctx.session.user.id),
|
|
eq(invoiceTemplates.type, input.type),
|
|
),
|
|
);
|
|
}
|
|
|
|
const [template] = await ctx.db
|
|
.insert(invoiceTemplates)
|
|
.values({ ...input, createdById: ctx.session.user.id })
|
|
.returning();
|
|
|
|
return template;
|
|
}),
|
|
|
|
update: protectedProcedure
|
|
.input(updateTemplateSchema)
|
|
.mutation(async ({ ctx, input }) => {
|
|
const { id, ...data } = input;
|
|
|
|
const existing = await ctx.db.query.invoiceTemplates.findFirst({
|
|
where: and(
|
|
eq(invoiceTemplates.id, id),
|
|
eq(invoiceTemplates.createdById, ctx.session.user.id),
|
|
),
|
|
});
|
|
|
|
if (!existing) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Template not found",
|
|
});
|
|
}
|
|
|
|
// If setting as default, unset others of same type
|
|
if (data.isDefault) {
|
|
const type = data.type ?? existing.type;
|
|
await ctx.db
|
|
.update(invoiceTemplates)
|
|
.set({ isDefault: false })
|
|
.where(
|
|
and(
|
|
eq(invoiceTemplates.createdById, ctx.session.user.id),
|
|
eq(invoiceTemplates.type, type),
|
|
),
|
|
);
|
|
}
|
|
|
|
await ctx.db
|
|
.update(invoiceTemplates)
|
|
.set({ ...data, updatedAt: new Date() })
|
|
.where(eq(invoiceTemplates.id, id));
|
|
|
|
return { success: true };
|
|
}),
|
|
|
|
delete: protectedProcedure
|
|
.input(z.object({ id: z.string() }))
|
|
.mutation(async ({ ctx, input }) => {
|
|
const existing = await ctx.db.query.invoiceTemplates.findFirst({
|
|
where: and(
|
|
eq(invoiceTemplates.id, input.id),
|
|
eq(invoiceTemplates.createdById, ctx.session.user.id),
|
|
),
|
|
});
|
|
|
|
if (!existing) {
|
|
throw new TRPCError({
|
|
code: "NOT_FOUND",
|
|
message: "Template not found",
|
|
});
|
|
}
|
|
|
|
await ctx.db
|
|
.delete(invoiceTemplates)
|
|
.where(eq(invoiceTemplates.id, input.id));
|
|
|
|
return { success: true };
|
|
}),
|
|
});
|