feat: leads module — kanban board, aktiviteler, takvim entegrasyonu, müşteriye dönüştür

This commit is contained in:
kovakmedya
2026-05-01 03:26:31 +03:00
parent 9e1355a137
commit ea3d6f6045
13 changed files with 1590 additions and 0 deletions
+145
View File
@@ -0,0 +1,145 @@
"use server";
import { revalidatePath } from "next/cache";
import { ID, Permission, Role } from "node-appwrite";
import { DATABASE_ID, TABLES, type Lead, type LeadActivityType } from "./schema";
import { createAdminClient } from "./server";
import { requireTenant } from "./tenant-guard";
export type ActivityActionState = { ok: boolean; error?: string };
export async function addLeadActivityAction(
_prev: ActivityActionState,
formData: FormData,
): Promise<ActivityActionState> {
const leadId = String(formData.get("leadId") ?? "");
const type = String(formData.get("type") ?? "note") as LeadActivityType;
const content = String(formData.get("content") ?? "").trim();
const occurredAt = String(formData.get("occurredAt") ?? "") || new Date().toISOString();
if (!leadId || !content) return { ok: false, error: "Zorunlu alanlar eksik." };
let ctx;
try {
ctx = await requireTenant();
} catch {
return { ok: false, error: "Yetkiniz yok." };
}
try {
const { tablesDB } = createAdminClient();
const lead = (await tablesDB.getRow(DATABASE_ID, TABLES.leads, leadId)) as unknown as Lead;
if (lead.tenantId !== ctx.tenantId) return { ok: false, error: "Erişim engellendi." };
await tablesDB.createRow(
DATABASE_ID,
TABLES.leadActivities,
ID.unique(),
{
tenantId: ctx.tenantId,
createdBy: ctx.user.id,
leadId,
type,
content,
occurredAt,
},
[
Permission.read(Role.team(ctx.tenantId)),
Permission.update(Role.team(ctx.tenantId)),
Permission.delete(Role.team(ctx.tenantId, "owner")),
],
);
// Update lastContactAt on the lead
await tablesDB.updateRow(DATABASE_ID, TABLES.leads, leadId, {
lastContactAt: new Date().toISOString(),
});
revalidatePath("/leads");
return { ok: true };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : "Hata oluştu." };
}
}
export async function scheduleFollowUpAction(
_prev: ActivityActionState,
formData: FormData,
): Promise<ActivityActionState> {
const leadId = String(formData.get("leadId") ?? "");
const followUpAt = String(formData.get("followUpAt") ?? "");
const note = String(formData.get("note") ?? "").trim();
if (!leadId || !followUpAt) return { ok: false, error: "Tarih seçin." };
let ctx;
try {
ctx = await requireTenant();
} catch {
return { ok: false, error: "Yetkiniz yok." };
}
try {
const { tablesDB } = createAdminClient();
const lead = (await tablesDB.getRow(DATABASE_ID, TABLES.leads, leadId)) as unknown as Lead;
if (lead.tenantId !== ctx.tenantId) return { ok: false, error: "Erişim engellendi." };
const followUpDate = new Date(followUpAt);
const endDate = new Date(followUpDate.getTime() + 60 * 60 * 1000); // +1h
const permissions = [
Permission.read(Role.team(ctx.tenantId)),
Permission.update(Role.team(ctx.tenantId)),
Permission.delete(Role.team(ctx.tenantId, "owner")),
];
// Create calendar event
const event = await tablesDB.createRow(
DATABASE_ID,
TABLES.calendarEvents,
ID.unique(),
{
tenantId: ctx.tenantId,
createdBy: ctx.user.id,
title: `Takip: ${lead.contactName || lead.name}`,
description: note || `Lead takip görüşmesi — ${lead.name}`,
start: followUpDate.toISOString(),
end: endDate.toISOString(),
allDay: false,
leadId,
color: "#f97316", // orange — lead events
},
permissions,
);
// Update lead nextFollowUpAt + calendarEventId
await tablesDB.updateRow(DATABASE_ID, TABLES.leads, leadId, {
nextFollowUpAt: followUpDate.toISOString(),
calendarEventId: event.$id,
});
// Log activity
await tablesDB.createRow(
DATABASE_ID,
TABLES.leadActivities,
ID.unique(),
{
tenantId: ctx.tenantId,
createdBy: ctx.user.id,
leadId,
type: "meeting",
content: `Takip planlandı: ${followUpDate.toLocaleString("tr-TR")}${note ? `${note}` : ""}`,
calendarEventId: event.$id,
occurredAt: new Date().toISOString(),
},
permissions,
);
revalidatePath("/leads");
revalidatePath("/calendar");
return { ok: true };
} catch (e) {
return { ok: false, error: e instanceof Error ? e.message : "Hata oluştu." };
}
}