Files
kovakemlak-crm/src/lib/appwrite/activity-email-actions.ts
T
egecankomur 7c23a2b4ae feat: activity assignment + team view for owner/admin
db: activities.assigneeId column (string, optional)

activity-actions: assigneeId saved on create (default: self), cleared on update
validation: assigneeId added to activitySchema
schema: assigneeId field on Activity type

activity-form-sheet: Atanan Kişi dropdown for owner/admin with member list
activity-team-view: new component — activities grouped by assignee,
  completion/edit/delete actions, overdue indicator, member avatars
activities-client: Ekip tab (owner/admin only), members + currentUserId props
activities page: fetches team memberships + user details, passes to client
activity-email-actions: filter by assigneeId ?? createdBy for both me/team modes
2026-05-12 17:40:21 +03:00

171 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use server";
import { ID } from "node-appwrite";
import { Query } from "node-appwrite";
import { createAdminClient } from "./server";
import { requireTenant } from "./tenant-guard";
import { DATABASE_ID, TABLES, ACTIVITY_TYPE_LABELS, type Activity } from "./schema";
import type { AuthState } from "./auth-types";
export type SendTarget = "me" | "team";
export type SendSummaryState = AuthState & { sent?: number };
export async function sendDailySummaryAction(
_prev: SendSummaryState,
formData: FormData,
): Promise<SendSummaryState> {
let ctx;
try {
ctx = await requireTenant();
} catch {
return { ok: false, error: "Oturum geçersiz." };
}
const target = (formData.get("target") ?? "me") as SendTarget;
if (target === "team" && !["owner", "admin"].includes(ctx.role)) {
return { ok: false, error: "Bu işlem için yetkiniz yok." };
}
const { tablesDB, teams, users, messaging } = createAdminClient();
const todayStr = new Date().toISOString().split("T")[0]; // "YYYY-MM-DD"
const activitiesResult = await tablesDB.listRows({
databaseId: DATABASE_ID,
tableId: TABLES.activities,
queries: [
Query.equal("tenantId", ctx.tenantId),
Query.limit(500),
],
});
const allActivities = JSON.parse(JSON.stringify(activitiesResult.rows)) as Activity[];
const todayActivities = allActivities.filter(
(a) => a.dueDate?.startsWith(todayStr),
);
const dateLabel = new Date().toLocaleDateString("tr-TR", {
day: "numeric",
month: "long",
});
if (target === "me") {
const mine = todayActivities.filter(
(a) => (a.assigneeId ?? a.createdBy) === ctx.user.id,
);
if (mine.length === 0) {
return { ok: false, error: "Bugün için planlanmış aktiviteniz bulunmuyor." };
}
await messaging.createEmail(
ID.unique(),
`Bugünün Aktiviteleri — ${dateLabel}`,
buildEmailHtml(mine, ctx.user.name),
[],
[ctx.user.id],
[],
);
return { ok: true, sent: 1 };
}
// send_to_team: her üyeye kendi aktiviteleri
const membershipsResult = await teams.listMemberships(ctx.tenantId);
let sentCount = 0;
for (const m of membershipsResult.memberships) {
if (!m.userId || !m.confirm) continue;
const memberActivities = todayActivities.filter(
(a) => (a.assigneeId ?? a.createdBy) === m.userId,
);
if (memberActivities.length === 0) continue;
const member = await users.get(m.userId).catch(() => null);
if (!member) continue;
await messaging.createEmail(
ID.unique(),
`Bugünün Aktiviteleri — ${dateLabel}`,
buildEmailHtml(memberActivities, member.name),
[],
[m.userId],
[],
);
sentCount++;
}
if (sentCount === 0) {
return {
ok: false,
error: "Ekip üyelerinin bugün planlanmış aktivitesi bulunmuyor.",
};
}
return { ok: true, sent: sentCount };
}
function buildEmailHtml(activities: Activity[], name: string): string {
const today = new Date().toLocaleDateString("tr-TR", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
});
const rows = activities
.map((a) => {
const done = a.completedAt
? `<span style="color:#16a34a;font-weight:600;">✓ Tamamlandı</span>`
: `<span style="color:#94a3b8;">○ Bekliyor</span>`;
const type = ACTIVITY_TYPE_LABELS[a.type] ?? a.type;
const desc = a.description
? `<div style="color:#64748b;font-size:13px;margin-top:2px;">${a.description}</div>`
: "";
return `
<tr style="border-bottom:1px solid #f1f5f9;">
<td style="padding:12px 16px;">
<div style="font-weight:500;color:#1e293b;">${a.title}</div>
${desc}
</td>
<td style="padding:12px 16px;white-space:nowrap;">
<span style="background:#f1f5f9;color:#475569;font-size:12px;padding:2px 8px;border-radius:9999px;">${type}</span>
</td>
<td style="padding:12px 16px;white-space:nowrap;font-size:13px;">${done}</td>
</tr>`;
})
.join("");
return `<!DOCTYPE html>
<html><body style="margin:0;padding:0;background:#f8fafc;font-family:sans-serif;">
<div style="max-width:600px;margin:32px auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.1);">
<div style="background:linear-gradient(135deg,#1e3a5f,#2563eb);padding:32px 32px 24px;">
<p style="color:#93c5fd;font-size:13px;margin:0 0 6px;">Emlak CRM · Günlük Özet</p>
<h1 style="color:#fff;font-size:22px;margin:0;">Merhaba, ${name}</h1>
<p style="color:#bfdbfe;font-size:14px;margin:8px 0 0;">${today}</p>
</div>
<div style="padding:24px 32px 8px;">
<p style="color:#475569;font-size:14px;margin:0 0 16px;">
Bugün için <strong>${activities.length} aktivite</strong> planlanmış:
</p>
<table style="width:100%;border-collapse:collapse;border:1px solid #e2e8f0;border-radius:8px;overflow:hidden;">
<thead>
<tr style="background:#f8fafc;">
<th style="padding:10px 16px;text-align:left;color:#64748b;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;">Aktivite</th>
<th style="padding:10px 16px;text-align:left;color:#64748b;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;">Tür</th>
<th style="padding:10px 16px;text-align:left;color:#64748b;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;">Durum</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
<div style="padding:24px 32px;">
<p style="color:#94a3b8;font-size:12px;margin:0;">
Bu e-posta Emlak CRM tarafından otomatik olarak gönderilmiştir.
</p>
</div>
</div>
</body></html>`;
}