feat: show the full work history and load every lead stage on the board

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-14 19:47:32 +03:30
co-authored by Cursor
parent 2cc81bb596
commit 831c682eb3
13 changed files with 669 additions and 66 deletions
+78
View File
@@ -0,0 +1,78 @@
const FIELD_LABELS: Record<string, string> = {
title: 'عنوان',
name: 'نام',
firstName: 'نام',
lastName: 'نام خانوادگی',
fullName: 'نام',
description: 'توضیحات',
body: 'متن',
mobile: 'موبایل',
phone: 'تلفن',
email: 'ایمیل',
status: 'وضعیت',
stageId: 'مرحله',
priority: 'اولویت',
dueAt: 'موعد',
amount: 'مبلغ',
subject: 'موضوع',
type: 'نوع',
city: 'شهر',
address: 'آدرس',
notes: 'یادداشت',
contactName: 'نام مخاطب',
};
const SECRET = /password|token|secret|otp|pin/i;
function textOf(value: unknown) {
if (typeof value === 'string') return value.trim().replace(/\s+/g, ' ');
if (typeof value === 'number' || typeof value === 'boolean') return String(value);
return '';
}
function labelOf(source: unknown) {
if (!source || typeof source !== 'object' || Array.isArray(source)) return '';
const body = source as Record<string, unknown>;
const person = [textOf(body.firstName), textOf(body.lastName)].filter(Boolean).join(' ');
return (
person ||
textOf(body.title) ||
textOf(body.name) ||
textOf(body.subject) ||
textOf(body.fullName)
).slice(0, 120);
}
function changesOf(source: unknown) {
if (!source || typeof source !== 'object' || Array.isArray(source)) return '';
const bits: string[] = [];
for (const [key, value] of Object.entries(source as Record<string, unknown>)) {
if (SECRET.test(key)) continue;
const name = FIELD_LABELS[key];
if (!name) continue;
if (key.endsWith('Id')) {
bits.push(name);
} else {
const text = textOf(value).slice(0, 40);
if (!text) continue;
bits.push(`${name}: ${text}`);
}
if (bits.length >= 4) break;
}
return bits.join('، ');
}
export function auditStory(
method: string,
requestBody: unknown,
responseBody: unknown,
) {
const label = labelOf(responseBody) || labelOf(requestBody);
const detail = method === 'DELETE' ? '' : changesOf(requestBody);
const summary = [label, detail].filter(Boolean).join(' — ').slice(0, 500);
return {
label,
detail: detail.slice(0, 240),
summary,
};
}