feat(10-03): add LogActivityModal component and logActivity server action
- Created LogActivityModal.tsx with form for registering activities (call/email/meeting/note) - Added type dropdown, date picker, optional duration, notes textarea - Integrated createActivitySchema validation from lead-validators - Added logActivity server action to create activity and auto-update lead.last_contact_date - Updated LeadDetail.tsx to import and render LogActivityModal button - Revalidates lead detail page on successful activity creation Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
|
||||
import { z } from "zod";
|
||||
import { db } from "@/db";
|
||||
import { leads } from "@/db/schema";
|
||||
import { leads, quotes } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { createLeadSchema, updateLeadSchema } from "@/lib/lead-validators";
|
||||
import { createLeadSchema, updateLeadSchema, createActivitySchema } from "@/lib/lead-validators";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { createActivity, updateLeadStage } from "@/lib/lead-service";
|
||||
|
||||
export async function createLead(data: z.infer<typeof createLeadSchema>) {
|
||||
const parsed = createLeadSchema.safeParse(data);
|
||||
@@ -80,3 +81,86 @@ export async function deleteLead(id: string) {
|
||||
return { success: false, error: "Errore nell'eliminazione" };
|
||||
}
|
||||
}
|
||||
|
||||
export async function logActivity(
|
||||
data: z.infer<typeof createActivitySchema>
|
||||
) {
|
||||
const parsed = createActivitySchema.safeParse(data);
|
||||
|
||||
if (!parsed.success) {
|
||||
return { success: false, error: parsed.error.issues[0].message };
|
||||
}
|
||||
|
||||
try {
|
||||
const activity = await createActivity({
|
||||
lead_id: parsed.data.lead_id,
|
||||
type: parsed.data.type,
|
||||
activity_date: new Date(parsed.data.activity_date),
|
||||
duration_minutes: parsed.data.duration_minutes,
|
||||
notes: parsed.data.notes,
|
||||
});
|
||||
|
||||
revalidatePath(`/admin/leads/${parsed.data.lead_id}`);
|
||||
return { success: true, activity };
|
||||
} catch (error) {
|
||||
console.error("logActivity error:", error);
|
||||
return { success: false, error: "Errore nella registrazione" };
|
||||
}
|
||||
}
|
||||
|
||||
const assignQuoteSchema = z.object({
|
||||
lead_id: z.string().min(1),
|
||||
quote_token: z.string().optional(),
|
||||
generate_new: z.boolean(),
|
||||
});
|
||||
|
||||
export async function assignQuoteToLead(
|
||||
data: z.infer<typeof assignQuoteSchema>
|
||||
) {
|
||||
const parsed = assignQuoteSchema.safeParse(data);
|
||||
|
||||
if (!parsed.success) {
|
||||
return { success: false, error: parsed.error.issues[0].message };
|
||||
}
|
||||
|
||||
try {
|
||||
if (parsed.data.generate_new) {
|
||||
// Redirect handled on client; this is a no-op
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// Link quote to lead and update lead status
|
||||
const token = parsed.data.quote_token;
|
||||
const leadId = parsed.data.lead_id;
|
||||
|
||||
if (!token) {
|
||||
return { success: false, error: "Token richiesto" };
|
||||
}
|
||||
|
||||
// Find quote by token
|
||||
const [quote] = await db
|
||||
.select()
|
||||
.from(quotes)
|
||||
.where(eq(quotes.token, token))
|
||||
.limit(1);
|
||||
|
||||
if (!quote) {
|
||||
return { success: false, error: "Preventivo non trovato" };
|
||||
}
|
||||
|
||||
// Update quote to link to lead (if not already linked)
|
||||
await db
|
||||
.update(quotes)
|
||||
.set({ lead_id: leadId })
|
||||
.where(eq(quotes.id, quote.id));
|
||||
|
||||
// Update lead status to "proposal_sent"
|
||||
await updateLeadStage(leadId, "proposal_sent");
|
||||
|
||||
revalidatePath(`/admin/leads/${leadId}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error("assignQuoteToLead error:", error);
|
||||
return { success: false, error: "Errore nell'assegnazione" };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user