import { CURRENCIES, DEAL_TYPES, PROPERTY_TYPES, REGIONS } from '@lr/shared'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Bot, Copy, ExternalLink, EyeOff, MessageCircle, Pencil, Phone, Send, Star, Trash2, Undo2, UserX } from 'lucide-react'; import { useEffect, useState } from 'react'; import { Link } from 'react-router'; import { api, type Me } from '../api.ts'; import { TelegramLink } from '../pages/Settings.tsx'; import { ErrorNote } from '../components/ui.tsx'; import { useAction } from '../hooks.ts'; import { useMembers, usePipeline, type LeadCard } from './api.ts'; import { CommissionBlock } from './commission.tsx'; import { Features } from './household.tsx'; import { useCrm } from './i18n.ts'; import { CommentBox, DealNote, Dialog, Drawer, History, NextAction, StatusChip, useSpecs, useStatusChange } from './parts.tsx'; /** Насколько номеру можно доверять: совпал у двух компаний — 80 %, у трёх и больше — 100 %. */ function Confidence({ v }: { v: { votes: number; confidence: number | null } }) { const { c } = useCrm(); return ( {v.confidence ? c.sh.confidence(v.confidence, v.votes) : c.sh.unconfirmed} ); } const EDIT_FIELDS = ['title', 'description', 'dealType', 'propertyType', 'region', 'district', 'address', 'price', 'currency', 'rooms', 'area', 'floor', 'floorsTotal', 'authorName'] as const; /** Правка данных объекта. Отправляются только изменённые поля; пустое поле стирает значение. */ function LeadEditDialog({ lead, onClose }: { lead: LeadCard; onClose: () => void }) { const { c, t } = useCrm(); const d = c.dlg; const initial = Object.fromEntries(EDIT_FIELDS.map((k) => [k, lead[k] == null ? '' : String(lead[k])])) as Record<(typeof EDIT_FIELDS)[number], string>; const [f, setF] = useState(initial); const save = useAction( () => api(`/leads/${lead.id}`, { method: 'PATCH', body: Object.fromEntries(EDIT_FIELDS.filter((k) => f[k] !== initial[k]).map((k) => [k, f[k]])) }), ['lead', lead.id], ); const changed = EDIT_FIELDS.some((k) => f[k] !== initial[k]); const input = (key: (typeof EDIT_FIELDS)[number], label: string, props: React.InputHTMLAttributes = {}) => (
setF({ ...f, [key]: e.target.value })} {...props} />
); const select = (key: (typeof EDIT_FIELDS)[number], label: string, options: [string, string][]) => (
); // Значение из объявления, которого нет в списке (например, регион), остаётся выбранным. const withCurrent = (key: (typeof EDIT_FIELDS)[number], options: [string, string][]) => initial[key] && !options.some(([v]) => v === initial[key]) ? [...options, [initial[key], initial[key]] as [string, string]] : options; return (
{ e.preventDefault(); if (changed) save.mutate(undefined, { onSuccess: onClose }); else onClose(); }}> {input('title', d.title, { required: true, maxLength: 200 })}
{select('dealType', c.filters.dealType, [['', '—'], ...DEAL_TYPES.map((x) => [x, c.deal[x]] as [string, string])])} {select('propertyType', c.filters.types, withCurrent('propertyType', [['', '—'], ...PROPERTY_TYPES.map((x) => [x, c.ptype[x]] as [string, string])]))} {select('region', c.filters.region, withCurrent('region', [['', '—'], ...REGIONS.map((r) => [r, r] as [string, string])]))} {input('district', c.col.place, { maxLength: 60 })}
{input('address', d.address, { maxLength: 300 })}
{input('price', d.price, { type: 'number', min: 0, step: 'any' })} {select('currency', d.currency, withCurrent('currency', CURRENCIES.map((x) => [x, x])))} {input('rooms', d.rooms, { type: 'number', min: 0, max: 50 })} {input('area', d.area, { type: 'number', min: 0, step: 'any' })} {input('floor', d.floor, { type: 'number', min: -5, max: 200 })} {input('floorsTotal', d.floors, { type: 'number', min: 0, max: 200 })}
{input('authorName', d.author, { maxLength: 100 })}