153 lines
4.1 KiB
TypeScript
153 lines
4.1 KiB
TypeScript
import { v } from "convex/values";
|
|
|
|
import { normalizeListLimit } from "./domain";
|
|
import { mutation, query } from "./_generated/server";
|
|
|
|
export const create = mutation({
|
|
args: {
|
|
campaignId: v.optional(v.id("campaigns")),
|
|
companyName: v.string(),
|
|
niche: v.optional(v.string()),
|
|
address: v.optional(v.string()),
|
|
city: v.optional(v.string()),
|
|
postalCode: v.optional(v.string()),
|
|
googlePlaceId: v.optional(v.string()),
|
|
googleMapsUrl: v.optional(v.string()),
|
|
websiteDomain: v.optional(v.string()),
|
|
phone: v.optional(v.string()),
|
|
email: v.optional(v.string()),
|
|
emailSource: v.optional(v.string()),
|
|
contactPerson: v.optional(v.string()),
|
|
priority: v.optional(
|
|
v.union(
|
|
v.literal("high"),
|
|
v.literal("medium"),
|
|
v.literal("low"),
|
|
v.literal("defer"),
|
|
),
|
|
),
|
|
contactStatus: v.optional(
|
|
v.union(
|
|
v.literal("new"),
|
|
v.literal("missing_contact"),
|
|
v.literal("audit_ready"),
|
|
v.literal("outreach_ready"),
|
|
v.literal("contacted"),
|
|
v.literal("replied"),
|
|
v.literal("do_not_contact"),
|
|
),
|
|
),
|
|
notes: v.optional(v.string()),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const now = Date.now();
|
|
|
|
return await ctx.db.insert("leads", {
|
|
...args,
|
|
priority: args.priority ?? "medium",
|
|
contactStatus: args.contactStatus ?? "new",
|
|
duplicateStatus: "unchecked",
|
|
blacklistStatus: "clear",
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
});
|
|
},
|
|
});
|
|
|
|
export const get = query({
|
|
args: { id: v.id("leads") },
|
|
handler: async (ctx, args) => {
|
|
return await ctx.db.get(args.id);
|
|
},
|
|
});
|
|
|
|
export const list = query({
|
|
args: {
|
|
campaignId: v.optional(v.id("campaigns")),
|
|
contactStatus: v.optional(
|
|
v.union(
|
|
v.literal("new"),
|
|
v.literal("missing_contact"),
|
|
v.literal("audit_ready"),
|
|
v.literal("outreach_ready"),
|
|
v.literal("contacted"),
|
|
v.literal("replied"),
|
|
v.literal("do_not_contact"),
|
|
),
|
|
),
|
|
limit: v.optional(v.number()),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const limit = normalizeListLimit(args.limit);
|
|
|
|
if (args.campaignId) {
|
|
const campaignId = args.campaignId;
|
|
|
|
return await ctx.db
|
|
.query("leads")
|
|
.withIndex("by_campaignId", (q) => q.eq("campaignId", campaignId))
|
|
.order("desc")
|
|
.take(limit);
|
|
}
|
|
|
|
if (args.contactStatus) {
|
|
const contactStatus = args.contactStatus;
|
|
|
|
return await ctx.db
|
|
.query("leads")
|
|
.withIndex("by_contactStatus", (q) =>
|
|
q.eq("contactStatus", contactStatus),
|
|
)
|
|
.order("desc")
|
|
.take(limit);
|
|
}
|
|
|
|
return await ctx.db.query("leads").order("desc").take(limit);
|
|
},
|
|
});
|
|
|
|
export const listFunnel = query({
|
|
args: {
|
|
limit: v.optional(v.number()),
|
|
},
|
|
handler: async (ctx, args) => {
|
|
const limit = normalizeListLimit(args.limit);
|
|
const leads = await ctx.db.query("leads").order("desc").take(limit);
|
|
|
|
return await Promise.all(
|
|
leads.map(async (lead) => {
|
|
const outreach = await ctx.db
|
|
.query("outreachRecords")
|
|
.withIndex("by_leadId", (q) => q.eq("leadId", lead._id))
|
|
.order("desc")
|
|
.take(1);
|
|
const latestOutreach = outreach[0] ?? null;
|
|
|
|
return {
|
|
id: lead._id,
|
|
companyName: lead.companyName,
|
|
niche: lead.niche ?? null,
|
|
address: lead.address ?? null,
|
|
city: lead.city ?? null,
|
|
postalCode: lead.postalCode ?? null,
|
|
priority: lead.priority,
|
|
contactStatus: lead.contactStatus,
|
|
blacklistStatus: lead.blacklistStatus,
|
|
email: lead.email ?? null,
|
|
phone: lead.phone ?? null,
|
|
contactPerson: lead.contactPerson ?? null,
|
|
websiteDomain: lead.websiteDomain ?? null,
|
|
outreach: latestOutreach
|
|
? {
|
|
approvalStatus: latestOutreach.approvalStatus,
|
|
sendStatus: latestOutreach.sendStatus,
|
|
responseStatus: latestOutreach.responseStatus,
|
|
salesStatus: latestOutreach.salesStatus,
|
|
}
|
|
: null,
|
|
};
|
|
}),
|
|
);
|
|
},
|
|
});
|