import { formatJalaliNumeric } from '@/lib/jalali'; export type LeadNoteFile = { name: string; mime: string; dataUrl: string | null; }; export type LeadNoteRow = { label: string; text: string; file: LeadNoteFile | null; }; export type LeadNoteView = { mode: 'prose' | 'answers'; prose: string; rows: LeadNoteRow[]; }; const UTM_LABELS: Record = { utm_source: 'منبع', utm_medium: 'رسانه', utm_campaign: 'کمپین', utm_term: 'عبارت', utm_content: 'محتوا', صفحه: 'صفحه', ارجاع: 'ارجاع', }; /** یادداشت لید را اگر جواب فرم باشد به ردیف سؤال/جواب تبدیل می‌کند. */ export function parseLeadFormNotes(raw: string | null | undefined): LeadNoteView { const text = (raw ?? '').trim(); if (!text) return { mode: 'prose', prose: '', rows: [] }; const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean); const rows: LeadNoteRow[] = []; const prose: string[] = []; for (const line of lines) { const row = parseAnswerLine(line); if (row) rows.push(row); else prose.push(line); } const answersAreMain = rows.length >= 2 && rows.length >= prose.length; if (!answersAreMain) return { mode: 'prose', prose: text, rows: [] }; return { mode: 'answers', prose: prose.join('\n'), rows }; } export function leadNoteFileIsImage(file: LeadNoteFile): boolean { return ( file.mime.startsWith('image/') || (file.dataUrl ?? '').startsWith('data:image/') || /\.(png|jpe?g|gif|webp)$/i.test(file.name) ); } export type NoteChip = | { kind: 'text'; text: string } | { kind: 'image'; src: string; name: string } | { kind: 'link'; href: string; label: string }; const FILE_BLOB = /\{[^{}]*"dataUrl"\s*:\s*"data:[^"]+"[^{}]*\}/gi; const DATA_IMAGE = /data:image\/[a-z0-9.+-]+;base64,[a-z0-9+/=]+/gi; const HTTP_URL = /https?:\/\/[^\s<>"']+/gi; /** متن یادداشت را به نوشته، لینک کوتاه و تصویر جدا می‌کند. */ export function noteChips(raw: string | null | undefined): NoteChip[] { const text = raw ?? ''; if (!text.trim()) return []; const chips: NoteChip[] = []; const token = new RegExp( `${FILE_BLOB.source}|${DATA_IMAGE.source}|${HTTP_URL.source}`, 'gi', ); let cursor = 0; for (const match of text.matchAll(token)) { const index = match.index ?? 0; pushText(chips, text.slice(cursor, index)); chips.push(chipForToken(match[0])); cursor = index + match[0].length; } pushText(chips, text.slice(cursor)); return chips; } function pushText(chips: NoteChip[], value: string) { if (!value) return; const prev = chips[chips.length - 1]; if (prev?.kind === 'text') prev.text += value; else chips.push({ kind: 'text', text: value }); } function chipForToken(token: string): NoteChip { if (token.startsWith('{')) { const file = readFileValue(token); if (file?.dataUrl && leadNoteFileIsImage(file)) { return { kind: 'image', src: file.dataUrl, name: file.name }; } if (file?.dataUrl) return { kind: 'link', href: file.dataUrl, label: file.name }; return { kind: 'text', text: file?.name || 'فایل پیوست' }; } if (token.startsWith('data:image/')) { return { kind: 'image', src: token, name: 'تصویر پیوست' }; } if (isImageUrl(token)) return { kind: 'image', src: token, name: shortUrl(token) }; return { kind: 'link', href: token, label: shortUrl(token) }; } function isImageUrl(href: string): boolean { return /\.(png|jpe?g|gif|webp)(\?.*)?$/i.test(href); } function shortUrl(href: string): string { try { const url = new URL(href); const file = decodeURIComponent(url.pathname.split('/').filter(Boolean).pop() ?? ''); const label = file || url.hostname; return label.length > 42 ? `${label.slice(0, 24)}…${label.slice(-10)}` : label; } catch { return href.length > 42 ? `${href.slice(0, 28)}…` : href; } } /** فیلد تاریخ تولد روی خود لید هست؛ جواب فرم نباید دوباره ردیف بسازد. */ export function isBirthDateAnswer(label: string): boolean { return label.trim() === 'تاریخ تولد'; } function parseAnswerLine(line: string): LeadNoteRow | null { const splitAt = line.indexOf(':'); if (splitAt <= 0 || splitAt > 200) return null; const label = line.slice(0, splitAt).trim(); const value = line.slice(splitAt + 1).trim(); if (!label || !value || looksLikeUrl(label)) return null; if (label === 'UTM') { return { label: 'اطلاعات ورود', text: humanizeUtm(value), file: null }; } const file = readFileValue(value); if (file) return { label, text: file.name, file }; return { label, text: humanizePlain(value), file: null }; } function looksLikeUrl(label: string): boolean { return /^https?:\/\//i.test(label); } function humanizePlain(value: string): string { if (/^\d{4}-\d{2}-\d{2}$/.test(value)) { const date = new Date(`${value}T12:00:00`); if (!Number.isNaN(date.getTime())) return formatJalaliNumeric(date); } if (value === 'true') return 'بله'; if (value === 'false') return 'خیر'; return value; } function humanizeUtm(value: string): string { return value .split('|') .map((part) => part.trim()) .filter(Boolean) .map((part) => { const eq = part.indexOf('='); if (eq <= 0) return part; const key = part.slice(0, eq).trim(); const val = part.slice(eq + 1).trim(); return `${UTM_LABELS[key] ?? key}: ${val}`; }) .join('، '); } function readFileValue(value: string): LeadNoteFile | null { if (!value.startsWith('{') && !value.includes('"dataUrl"') && !value.includes('"name"')) { return null; } try { return asFile(JSON.parse(value)); } catch { const name = value.match(/"name"\s*:\s*"([^"]+)"/)?.[1]?.trim(); const mime = value.match(/"mime"\s*:\s*"([^"]+)"/)?.[1]?.trim() ?? ''; if (name) return { name, mime, dataUrl: null }; if (value.includes('data:')) return { name: 'فایل پیوست', mime: '', dataUrl: null }; return null; } } function asFile(value: unknown): LeadNoteFile | null { if (!value || typeof value !== 'object' || Array.isArray(value)) return null; const row = value as { name?: unknown; mime?: unknown; dataUrl?: unknown }; const name = typeof row.name === 'string' ? row.name.trim() : ''; const mime = typeof row.mime === 'string' ? row.mime.trim() : ''; const dataUrl = typeof row.dataUrl === 'string' && row.dataUrl.startsWith('data:') ? row.dataUrl : null; if (!name && !dataUrl) return null; return { name: name || 'فایل پیوست', mime: mime || (dataUrl?.startsWith('data:image/') ? 'image/' : ''), dataUrl, }; }