feat: single ФИО field with autocomplete replaces 3 separate name inputs
- BookingModal (room tab): replace Фамилия/Имя/Отчество grid with single 'ФИО *' input; parseFullName() splits by spaces into last/first/middle; DB search via api.guests.list (debounced 300ms); dropdown shows current bookings + DB guests with phone/email autofill - BookingDetailPanel: merge Фамилия (with autocomplete) + Имя + Отчество into single ФИО * input; existing acQuery/acResults infrastructure reused; parseFio() updates all 3 bgForm fields on every keystroke; openEditForm initializes ФИО from full name - RentalBookingModal + BookingModal rental tab: include middleName when displaying DB guest results and when setting guestName on selection; update type to include middleName field Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -146,10 +146,16 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
setEarlyOpen(false)
|
setEarlyOpen(false)
|
||||||
}, [booking.id])
|
}, [booking.id])
|
||||||
|
|
||||||
|
// Parse ФИО string into form fields
|
||||||
|
const parseFio = (val: string) => {
|
||||||
|
const parts = val.trim().split(/\s+/)
|
||||||
|
return { last_name: parts[0] ?? '', first_name: parts[1] ?? '', middle_name: parts.slice(2).join(' ') || undefined }
|
||||||
|
}
|
||||||
|
|
||||||
// Debounced guest autocomplete
|
// Debounced guest autocomplete
|
||||||
const handleAcInput = (val: string) => {
|
const handleAcInput = (val: string) => {
|
||||||
setBgForm(f => ({ ...f, last_name: val }))
|
|
||||||
setAcQuery(val)
|
setAcQuery(val)
|
||||||
|
setBgForm(f => ({ ...f, ...parseFio(val) }))
|
||||||
if (acTimerRef.current) clearTimeout(acTimerRef.current)
|
if (acTimerRef.current) clearTimeout(acTimerRef.current)
|
||||||
if (val.length < 2 || !slug) { setAcResults([]); return }
|
if (val.length < 2 || !slug) { setAcResults([]); return }
|
||||||
acTimerRef.current = setTimeout(() => {
|
acTimerRef.current = setTimeout(() => {
|
||||||
@@ -162,10 +168,11 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectAcGuest = (g: GuestApiType) => {
|
const selectAcGuest = (g: GuestApiType) => {
|
||||||
|
const fullName = [g.lastName, g.firstName, g.middleName].filter(Boolean).join(' ')
|
||||||
setBgForm(f => ({
|
setBgForm(f => ({
|
||||||
...f,
|
...f,
|
||||||
first_name: g.firstName,
|
first_name: g.firstName,
|
||||||
last_name: g.lastName,
|
last_name: g.lastName,
|
||||||
middle_name: g.middleName ?? undefined,
|
middle_name: g.middleName ?? undefined,
|
||||||
birth_date: g.birthDate ?? undefined,
|
birth_date: g.birthDate ?? undefined,
|
||||||
passport_series: g.passportSeries ?? undefined,
|
passport_series: g.passportSeries ?? undefined,
|
||||||
@@ -174,7 +181,7 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
passport_issue_date: g.passportIssueDate ?? undefined,
|
passport_issue_date: g.passportIssueDate ?? undefined,
|
||||||
nationality: g.nationality ?? undefined,
|
nationality: g.nationality ?? undefined,
|
||||||
}))
|
}))
|
||||||
setAcQuery(g.lastName)
|
setAcQuery(fullName)
|
||||||
setAcResults([])
|
setAcResults([])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +226,7 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
passport_issue_date: bg.passportIssueDate?.slice(0, 10) ?? undefined,
|
passport_issue_date: bg.passportIssueDate?.slice(0, 10) ?? undefined,
|
||||||
nationality: bg.nationality ?? undefined,
|
nationality: bg.nationality ?? undefined,
|
||||||
})
|
})
|
||||||
setAcQuery('')
|
setAcQuery([bg.lastName, bg.firstName, bg.middleName].filter(Boolean).join(' '))
|
||||||
setAcResults([])
|
setAcResults([])
|
||||||
setShowGuestForm(true)
|
setShowGuestForm(true)
|
||||||
}
|
}
|
||||||
@@ -938,15 +945,15 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
{editingBg ? 'Редактировать гостя' : 'Добавить гостя'}
|
{editingBg ? 'Редактировать гостя' : 'Добавить гостя'}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Autocomplete last name */}
|
{/* ФИО with autocomplete */}
|
||||||
<div ref={acWrapRef} className="relative">
|
<div ref={acWrapRef} className="relative">
|
||||||
<label className={lbl}>Фамилия *</label>
|
<label className={lbl}>ФИО *</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
className={fld}
|
className={fld}
|
||||||
value={bgForm.last_name ?? ''}
|
value={acQuery}
|
||||||
onChange={e => handleAcInput(e.target.value)}
|
onChange={e => handleAcInput(e.target.value)}
|
||||||
placeholder="Иванов"
|
placeholder="Иванов Иван Иванович"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
{acLoading && (
|
{acLoading && (
|
||||||
@@ -966,7 +973,7 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
<Search size={11} className="text-slate-400 shrink-0" />
|
<Search size={11} className="text-slate-400 shrink-0" />
|
||||||
<div>
|
<div>
|
||||||
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
|
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
|
||||||
{g.lastName} {g.firstName}
|
{[g.lastName, g.firstName, g.middleName].filter(Boolean).join(' ')}
|
||||||
</p>
|
</p>
|
||||||
{(g.passportSeries || g.passportNumber) && (
|
{(g.passportSeries || g.passportNumber) && (
|
||||||
<p className="text-xs text-slate-400 font-mono">
|
<p className="text-xs text-slate-400 font-mono">
|
||||||
@@ -981,18 +988,6 @@ export function BookingDetailPanel({ booking, room, rooms, allBookings, slug, on
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* First name + middle name */}
|
|
||||||
<div className="grid grid-cols-2 gap-2">
|
|
||||||
<div>
|
|
||||||
<label className={lbl}>Имя *</label>
|
|
||||||
<input className={fld} value={bgForm.first_name ?? ''} onChange={e => setBgForm(f => ({ ...f, first_name: e.target.value }))} placeholder="Иван" />
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className={lbl}>Отчество</label>
|
|
||||||
<input className={fld} value={bgForm.middle_name ?? ''} onChange={e => setBgForm(f => ({ ...f, middle_name: e.target.value }))} placeholder="Иванович" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Birth date + flags */}
|
{/* Birth date + flags */}
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -60,6 +60,15 @@ function fmtTime(min: number) {
|
|||||||
return m === 0 ? `${h}:00` : `${h}:${String(m).padStart(2, '0')}`
|
return m === 0 ? `${h}:00` : `${h}:${String(m).padStart(2, '0')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseFullName(full: string) {
|
||||||
|
const parts = full.trim().split(/\s+/)
|
||||||
|
return {
|
||||||
|
lastName: parts[0] ?? '',
|
||||||
|
firstName: parts[1] ?? '',
|
||||||
|
middleName: parts.slice(2).join(' '),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isConflict(bookings: Booking[], roomId: string, checkIn: string, checkOut: string, excludeId?: string) {
|
function isConflict(bookings: Booking[], roomId: string, checkIn: string, checkOut: string, excludeId?: string) {
|
||||||
if (!checkIn || !checkOut || checkIn >= checkOut) return false
|
if (!checkIn || !checkOut || checkIn >= checkOut) return false
|
||||||
return bookings.some(b =>
|
return bookings.some(b =>
|
||||||
@@ -105,10 +114,17 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
|
|||||||
const [rentalSaving, setRentalSaving] = useState(false)
|
const [rentalSaving, setRentalSaving] = useState(false)
|
||||||
// Rental guest autocomplete
|
// Rental guest autocomplete
|
||||||
const [rentalShowSugg, setRentalShowSugg] = useState(false)
|
const [rentalShowSugg, setRentalShowSugg] = useState(false)
|
||||||
const [rentalDbResults, setRentalDbResults] = useState<{ id: string; firstName: string; lastName: string; phone: string | null }[]>([])
|
const [rentalDbResults, setRentalDbResults] = useState<{ id: string; firstName: string; lastName: string; middleName: string | null; phone: string | null }[]>([])
|
||||||
const rentalSearchTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const rentalSearchTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
const rentalNameRef = useRef<HTMLDivElement>(null)
|
const rentalNameRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
|
// Guest autocomplete for room tab
|
||||||
|
const [guestFullName, setGuestFullName] = useState(existing?.guestName ?? '')
|
||||||
|
const [showGuestSugg, setShowGuestSugg] = useState(false)
|
||||||
|
const [guestDbResults, setGuestDbResults] = useState<{ id: string; firstName: string; lastName: string; middleName: string | null; phone: string | null; email: string | null }[]>([])
|
||||||
|
const guestSearchTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
const guestNameRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const rentalObj = rentalObjects?.find(o => o.id === rentalObjId) ?? rentalObjects?.[0]
|
const rentalObj = rentalObjects?.find(o => o.id === rentalObjId) ?? rentalObjects?.[0]
|
||||||
const rentalDayBookings = rentalBookings.filter(b =>
|
const rentalDayBookings = rentalBookings.filter(b =>
|
||||||
b.objectId === rentalObjId &&
|
b.objectId === rentalObjId &&
|
||||||
@@ -136,17 +152,29 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
|
|||||||
}
|
}
|
||||||
}, [rentalEffStart])
|
}, [rentalEffStart])
|
||||||
|
|
||||||
// Close rental guest suggestions on outside click
|
// Close suggestions on outside click
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (e: MouseEvent) => {
|
const handler = (e: MouseEvent) => {
|
||||||
if (rentalNameRef.current && !rentalNameRef.current.contains(e.target as Node)) {
|
if (rentalNameRef.current && !rentalNameRef.current.contains(e.target as Node)) {
|
||||||
setRentalShowSugg(false)
|
setRentalShowSugg(false)
|
||||||
}
|
}
|
||||||
|
if (guestNameRef.current && !guestNameRef.current.contains(e.target as Node)) {
|
||||||
|
setShowGuestSugg(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener('mousedown', handler)
|
document.addEventListener('mousedown', handler)
|
||||||
return () => document.removeEventListener('mousedown', handler)
|
return () => document.removeEventListener('mousedown', handler)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
// Guest suggestions (room tab)
|
||||||
|
const guestSugg = [
|
||||||
|
...(bookings ?? []).filter(b => b.status === 'checked_in'),
|
||||||
|
...(bookings ?? []).filter(b => b.status === 'confirmed'),
|
||||||
|
]
|
||||||
|
const guestFiltered = guestFullName.trim().length > 0
|
||||||
|
? guestSugg.filter(b => b.guestName.toLowerCase().includes(guestFullName.toLowerCase()))
|
||||||
|
: guestSugg
|
||||||
|
|
||||||
// Rental guest suggestions from current bookings
|
// Rental guest suggestions from current bookings
|
||||||
const rentalGuestSugg = [
|
const rentalGuestSugg = [
|
||||||
...(bookings ?? []).filter(b => b.status === 'checked_in'),
|
...(bookings ?? []).filter(b => b.status === 'checked_in'),
|
||||||
@@ -172,9 +200,10 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
|
|||||||
setRentalSaving(true)
|
setRentalSaving(true)
|
||||||
try {
|
try {
|
||||||
if (slug) {
|
if (slug) {
|
||||||
const parts = rentalGuest.trim().split(' ')
|
const { lastName: rLast, firstName: rFirst, middleName: rMid } = parseFullName(rentalGuest)
|
||||||
await api.guests.create(slug, {
|
await api.guests.create(slug, {
|
||||||
last_name: parts[0] ?? '', first_name: parts[1] ?? '',
|
last_name: rLast, first_name: rFirst,
|
||||||
|
middle_name: rMid || undefined,
|
||||||
phone: rentalPhone.trim() || undefined,
|
phone: rentalPhone.trim() || undefined,
|
||||||
}).catch(() => { /* ignore duplicate */ })
|
}).catch(() => { /* ignore duplicate */ })
|
||||||
}
|
}
|
||||||
@@ -590,7 +619,7 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
|
|||||||
.filter(g => !rentalFiltered.some(b => b.guestName === `${g.lastName} ${g.firstName}`))
|
.filter(g => !rentalFiltered.some(b => b.guestName === `${g.lastName} ${g.firstName}`))
|
||||||
.map(g => (
|
.map(g => (
|
||||||
<button key={g.id} type="button"
|
<button key={g.id} type="button"
|
||||||
onMouseDown={() => { setRentalGuest(`${g.lastName} ${g.firstName}`); setRentalPhone(g.phone ?? ''); setRentalShowSugg(false); setRentalDbResults([]) }}
|
onMouseDown={() => { setRentalGuest([g.lastName, g.firstName, g.middleName].filter(Boolean).join(' ')); setRentalPhone(g.phone ?? ''); setRentalShowSugg(false); setRentalDbResults([]) }}
|
||||||
className="w-full flex items-center gap-2.5 px-3 py-2.5 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors text-left"
|
className="w-full flex items-center gap-2.5 px-3 py-2.5 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors text-left"
|
||||||
>
|
>
|
||||||
<div className="w-2 h-2 rounded-full shrink-0 bg-slate-400" />
|
<div className="w-2 h-2 rounded-full shrink-0 bg-slate-400" />
|
||||||
@@ -841,30 +870,80 @@ export function BookingModal({ open, draft, rooms, bookings = [], onClose, onSav
|
|||||||
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
<label className="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-2">
|
||||||
Гость
|
Гость
|
||||||
</label>
|
</label>
|
||||||
<div className="grid grid-cols-3 gap-2">
|
<div>
|
||||||
<div>
|
<label className="block text-xs text-slate-500 dark:text-slate-400 mb-1">ФИО *</label>
|
||||||
<label className="block text-xs text-slate-500 dark:text-slate-400 mb-1">Фамилия *</label>
|
<div className="relative" ref={guestNameRef}>
|
||||||
<input
|
<input
|
||||||
type="text" className="input" placeholder="Иванов"
|
type="text" className="input" placeholder="Иванов Иван Иванович" autoComplete="off"
|
||||||
value={form.lastName}
|
value={guestFullName}
|
||||||
onChange={e => set('lastName', e.target.value)}
|
onFocus={() => setShowGuestSugg(true)}
|
||||||
/>
|
onChange={e => {
|
||||||
</div>
|
const val = e.target.value
|
||||||
<div>
|
setGuestFullName(val)
|
||||||
<label className="block text-xs text-slate-500 dark:text-slate-400 mb-1">Имя *</label>
|
const parsed = parseFullName(val)
|
||||||
<input
|
set('lastName', parsed.lastName)
|
||||||
type="text" className="input" placeholder="Иван"
|
set('firstName', parsed.firstName)
|
||||||
value={form.firstName}
|
set('middleName', parsed.middleName)
|
||||||
onChange={e => set('firstName', e.target.value)}
|
setShowGuestSugg(true)
|
||||||
/>
|
if (guestSearchTimer.current) clearTimeout(guestSearchTimer.current)
|
||||||
</div>
|
if (val.trim().length >= 2 && slug) {
|
||||||
<div>
|
guestSearchTimer.current = setTimeout(async () => {
|
||||||
<label className="block text-xs text-slate-500 dark:text-slate-400 mb-1">Отчество</label>
|
try {
|
||||||
<input
|
const results = await api.guests.list(slug, val.trim())
|
||||||
type="text" className="input" placeholder="Иванович"
|
setGuestDbResults(results.slice(0, 8))
|
||||||
value={form.middleName}
|
} catch { setGuestDbResults([]) }
|
||||||
onChange={e => set('middleName', e.target.value)}
|
}, 300)
|
||||||
|
} else {
|
||||||
|
setGuestDbResults([])
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{showGuestSugg && (guestFiltered.length > 0 || guestDbResults.length > 0) && (
|
||||||
|
<div className="absolute top-full left-0 right-0 mt-1 z-50 bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-600 shadow-xl overflow-hidden max-h-52 overflow-y-auto">
|
||||||
|
{guestFiltered.slice(0, 5).map(b => (
|
||||||
|
<button key={b.id} type="button"
|
||||||
|
onMouseDown={() => {
|
||||||
|
setGuestFullName(b.guestName)
|
||||||
|
const p = parseFullName(b.guestName)
|
||||||
|
set('lastName', p.lastName); set('firstName', p.firstName); set('middleName', p.middleName)
|
||||||
|
if (b.guestPhone) set('phone', b.guestPhone)
|
||||||
|
if (b.guestEmail) set('guestEmail', b.guestEmail)
|
||||||
|
setShowGuestSugg(false); setGuestDbResults([])
|
||||||
|
}}
|
||||||
|
className="w-full flex items-center gap-2.5 px-3 py-2.5 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors text-left"
|
||||||
|
>
|
||||||
|
<div className={cn('w-2 h-2 rounded-full shrink-0', b.status === 'checked_in' ? 'bg-emerald-500' : 'bg-sky-400')} />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-sm font-medium text-slate-800 dark:text-slate-200 truncate">{b.guestName}</p>
|
||||||
|
<p className="text-[11px] text-slate-400 truncate">{b.guestPhone || (b.status === 'checked_in' ? '🏠 Проживает' : '📅 Бронь')}</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{guestDbResults
|
||||||
|
.filter(g => !guestFiltered.some(b => b.guestName.startsWith(g.lastName)))
|
||||||
|
.map(g => {
|
||||||
|
const fullName = [g.lastName, g.firstName, g.middleName].filter(Boolean).join(' ')
|
||||||
|
return (
|
||||||
|
<button key={g.id} type="button"
|
||||||
|
onMouseDown={() => {
|
||||||
|
setGuestFullName(fullName)
|
||||||
|
set('lastName', g.lastName); set('firstName', g.firstName); set('middleName', g.middleName ?? '')
|
||||||
|
if (g.phone) set('phone', g.phone)
|
||||||
|
if (g.email) set('guestEmail', g.email)
|
||||||
|
setShowGuestSugg(false); setGuestDbResults([])
|
||||||
|
}}
|
||||||
|
className="w-full flex items-center gap-2.5 px-3 py-2.5 hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors text-left"
|
||||||
|
>
|
||||||
|
<div className="w-2 h-2 rounded-full shrink-0 bg-slate-400" />
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="text-sm font-medium text-slate-800 dark:text-slate-200 truncate">{fullName}</p>
|
||||||
|
<p className="text-[11px] text-slate-400 truncate">{g.phone ?? g.email ?? 'нет контактов'}</p>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-2 mt-2">
|
<div className="grid grid-cols-2 gap-2 mt-2">
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export function RentalBookingModal({
|
|||||||
const nameRef = useRef<HTMLDivElement>(null)
|
const nameRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
// DB guest search
|
// DB guest search
|
||||||
const [dbResults, setDbResults] = useState<{ id: string; firstName: string; lastName: string; phone: string | null }[]>([])
|
const [dbResults, setDbResults] = useState<{ id: string; firstName: string; lastName: string; middleName: string | null; phone: string | null }[]>([])
|
||||||
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
const searchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
|
|
||||||
@@ -395,13 +395,15 @@ export function RentalBookingModal({
|
|||||||
})}
|
})}
|
||||||
{/* DB guest search results */}
|
{/* DB guest search results */}
|
||||||
{dbResults
|
{dbResults
|
||||||
.filter(g => !filtered.some(b => b.guestName === `${g.lastName} ${g.firstName}`))
|
.filter(g => !filtered.some(b => b.guestName.startsWith(g.lastName)))
|
||||||
.map(g => (
|
.map(g => {
|
||||||
|
const fullName = [g.lastName, g.firstName, g.middleName].filter(Boolean).join(' ')
|
||||||
|
return (
|
||||||
<button
|
<button
|
||||||
key={g.id}
|
key={g.id}
|
||||||
type="button"
|
type="button"
|
||||||
onMouseDown={() => {
|
onMouseDown={() => {
|
||||||
setGuestName(`${g.lastName} ${g.firstName}`)
|
setGuestName(fullName)
|
||||||
setGuestPhone(g.phone ?? '')
|
setGuestPhone(g.phone ?? '')
|
||||||
setShowSuggestions(false)
|
setShowSuggestions(false)
|
||||||
setDbResults([])
|
setDbResults([])
|
||||||
@@ -411,13 +413,13 @@ export function RentalBookingModal({
|
|||||||
>
|
>
|
||||||
<div className="w-2 h-2 rounded-full shrink-0 bg-slate-400" />
|
<div className="w-2 h-2 rounded-full shrink-0 bg-slate-400" />
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<p className="text-sm font-medium text-slate-800 dark:text-slate-200 truncate">{g.lastName} {g.firstName}</p>
|
<p className="text-sm font-medium text-slate-800 dark:text-slate-200 truncate">{fullName}</p>
|
||||||
<p className="text-[11px] text-slate-400 truncate">
|
<p className="text-[11px] text-slate-400 truncate">
|
||||||
{g.phone ?? 'нет телефона'}
|
{g.phone ?? 'нет телефона'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
))
|
)})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user