120 lines
4.3 KiB
TypeScript
120 lines
4.3 KiB
TypeScript
'use client'
|
||
|
||
import React from 'react'
|
||
import Image from 'next/image'
|
||
import { useNavigationService } from '@/services/navigationService'
|
||
interface ObjectData {
|
||
object_id: string
|
||
title: string
|
||
description: string
|
||
image: string | null
|
||
location: string
|
||
floors?: number
|
||
area?: string
|
||
type?: string
|
||
status?: string
|
||
}
|
||
|
||
interface ObjectCardProps {
|
||
object: ObjectData
|
||
onSelect?: (objectId: string) => void
|
||
isSelected?: boolean
|
||
}
|
||
|
||
|
||
|
||
const ObjectCard: React.FC<ObjectCardProps> = ({ object, onSelect, isSelected = false }) => {
|
||
const navigationService = useNavigationService()
|
||
|
||
const handleCardClick = () => {
|
||
if (onSelect) {
|
||
onSelect(object.object_id)
|
||
}
|
||
// Навигация к дашборду с выбранным объектом
|
||
navigationService.selectObjectAndGoToDashboard(object.object_id, object.title)
|
||
}
|
||
|
||
|
||
|
||
// Возврат к тестовому изображению, если src отсутствует/некорректен; нормализация относительных путей
|
||
const resolveImageSrc = (src?: string | null): string => {
|
||
if (!src || typeof src !== 'string') return '/images/test_image.png'
|
||
let s = src.trim()
|
||
if (!s) return '/images/test_image.png'
|
||
|
||
// Нормализуем обратные слеши в стиле Windows
|
||
s = s.replace(/\\/g, '/')
|
||
const lower = s.toLowerCase()
|
||
|
||
// Обрабатываем явный плейсхолдер test_image.png только как заглушку
|
||
if (lower === 'test_image.png' || lower.endsWith('/test_image.png') || lower.includes('/public/images/test_image.png')) {
|
||
return '/images/test_image.png'
|
||
}
|
||
|
||
// Абсолютные URL
|
||
if (s.startsWith('http://') || s.startsWith('https://')) return s
|
||
|
||
// Пути, относительные к сайту
|
||
if (s.startsWith('/')) {
|
||
// Преобразуем /public/images/... в /images/...
|
||
if (/\/public\/images\//i.test(s)) {
|
||
return s.replace(/\/public\/images\//i, '/images/')
|
||
}
|
||
return s
|
||
}
|
||
|
||
// Нормализуем относительные имена ресурсов до путей сайта под /images
|
||
// Убираем ведущий 'public/', если он присутствует
|
||
s = s.replace(/^public\//i, '')
|
||
return s.startsWith('images/') ? `/${s}` : `/images/${s}`
|
||
}
|
||
|
||
const imgSrc = resolveImageSrc(object.image)
|
||
|
||
return (
|
||
<article
|
||
className={`flex flex-col w-full min-h-[340px] h-[340px] sm:h-auto sm:min-h-[300px] items-start gap-3 p-3 sm:p-4 relative bg-[#161824] rounded-[20px] overflow-hidden cursor-pointer transition-all duration-200 hover:bg-[#1a1d2e] border border-white/20 ${
|
||
isSelected ? 'ring-2 ring-blue-500' : ''
|
||
}`}
|
||
onClick={handleCardClick}
|
||
role="button"
|
||
tabIndex={0}
|
||
onKeyDown={(e) => {
|
||
if (e.key === 'Enter' || e.key === ' ') {
|
||
handleCardClick()
|
||
}
|
||
}}
|
||
>
|
||
<header className="flex flex-col sm:flex-row items-start sm:items-center gap-3 sm:gap-2 relative self-stretch w-full flex-[0_0_auto]">
|
||
<div className="flex-col items-start flex-1 grow flex relative min-w-0">
|
||
<h2 className="self-stretch mt-[-1.00px] font-medium text-white text-lg leading-7 relative tracking-[0] break-words">
|
||
{object.title}
|
||
</h2>
|
||
<p className="self-stretch font-normal text-[#71717a] text-sm leading-5 relative tracking-[0] break-words">
|
||
{object.description}
|
||
</p>
|
||
</div>
|
||
|
||
</header>
|
||
|
||
{/* Изображение объекта */}
|
||
<div className="relative flex-1 self-stretch w-full grow bg-[#f1f1f1] rounded-lg overflow-hidden min-h-[170px] sm:min-h-[200px]">
|
||
<Image
|
||
className="absolute w-full h-full top-0 left-0 object-cover"
|
||
alt={object.title}
|
||
src={imgSrc}
|
||
fill
|
||
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
|
||
onError={(e) => {
|
||
// Заглушка при ошибке загрузки изображения
|
||
const target = e.target as HTMLImageElement
|
||
target.src = '/images/test_image.png'
|
||
}}
|
||
/>
|
||
</div>
|
||
</article>
|
||
)
|
||
}
|
||
|
||
export default ObjectCard
|
||
export type { ObjectData, ObjectCardProps } |