Разработка интерфейса фронт

This commit is contained in:
iv_vuytsik
2025-09-05 03:16:17 +03:00
parent 6c2ea027a4
commit 4d6b7b48d7
35 changed files with 3806 additions and 276 deletions

View File

@@ -0,0 +1,103 @@
'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
location: string
floors?: number
area?: string
type?: string
status?: string
}
interface ObjectCardProps {
object: ObjectData
onSelect?: (objectId: string) => void
isSelected?: boolean
}
// Иконка редактирования
const EditIcon = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" />
</svg>
)
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)
}
const handleEditClick = (e: React.MouseEvent) => {
e.stopPropagation()
console.log('Edit object:', object.object_id)
// Логика редактирования объекта
}
return (
<article
className={`flex flex-col w-full min-h-[414px] h-[414px] sm:h-auto sm:min-h-[350px] items-start gap-4 p-4 sm:p-6 relative bg-[#161824] rounded-[20px] overflow-hidden cursor-pointer transition-all duration-200 hover:bg-[#1a1d2e] ${
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>
<button
className="inline-flex flex-shrink-0 bg-[#3193f5] h-10 items-center justify-center gap-2 px-3 sm:px-4 py-2 relative rounded-md transition-colors duration-200 hover:bg-[#2563eb] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 w-full sm:w-auto"
aria-label={`Изменить ${object.title}`}
onClick={handleEditClick}
>
<EditIcon className="!relative !w-4 !h-4 text-white flex-shrink-0" />
<span className="font-medium text-white text-sm leading-5 relative tracking-[0] sm:whitespace-nowrap">
Изменить
</span>
</button>
</header>
{/* Изображение объекта */}
<div className="relative flex-1 self-stretch w-full grow bg-[#f1f1f1] rounded-lg overflow-hidden min-h-[200px] sm:min-h-[250px]">
<Image
className="absolute w-full h-full top-0 left-0 object-cover"
alt={object.title}
src={object.image}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
onError={(e) => {
// Заглушка при ошибке загрузки изображения
const target = e.target as HTMLImageElement
target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDUyIiBoZWlnaHQ9IjMwMiIgdmlld0JveD0iMCAwIDQ1MiAzMDIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI0NTIiIGhlaWdodD0iMzAyIiBmaWxsPSIjRjFGMUYxIi8+CjxwYXRoIGQ9Ik0yMjYgMTUxTDI0NiAxMzFMMjY2IDE1MUwyNDYgMTcxTDIyNiAxNTFaIiBmaWxsPSIjOTk5OTk5Ii8+Cjx0ZXh0IHg9IjIyNiIgeT0iMTkwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjOTk5OTk5IiBmb250LXNpemU9IjE0Ij7QntCx0YrQtdC60YI8L3RleHQ+Cjwvc3ZnPgo='
}}
/>
</div>
</article>
)
}
export default ObjectCard
export type { ObjectData, ObjectCardProps }

View File

@@ -0,0 +1,102 @@
'use client'
import React from 'react'
import ObjectCard, { ObjectData } from './ObjectCard'
interface ObjectGalleryProps {
objects: ObjectData[]
title?: string
onObjectSelect?: (objectId: string) => void
selectedObjectId?: string | null
className?: string
}
const BackIcon = ({ className }: { className?: string }) => (
<svg className={className} fill="currentColor" viewBox="0 0 24 24">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
</svg>
)
const ObjectGallery: React.FC<ObjectGalleryProps> = ({
objects,
title = 'Объекты',
onObjectSelect,
selectedObjectId,
className = ''
}) => {
const handleObjectSelect = (objectId: string) => {
if (onObjectSelect) {
onObjectSelect(objectId)
}
}
const handleBackClick = () => {
console.log('Back clicked')
}
return (
<div className={`flex flex-col items-start relative bg-[#0e111a] min-h-screen ${className}`}>
<main className="relative self-stretch w-full">
<div className="flex flex-col w-full items-start gap-6 p-4 sm:p-8 lg:p-16">
<header className="flex flex-col items-start gap-9 relative self-stretch w-full flex-[0_0_auto]">
<nav className="items-center gap-4 self-stretch w-full flex-[0_0_auto] flex relative">
<button
className="flex w-10 bg-[#161824] h-10 items-center justify-center gap-2 px-2 py-2 relative rounded-md transition-colors duration-200 hover:bg-[#1a1d2e] focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
aria-label="Назад"
onClick={handleBackClick}
>
<BackIcon className="relative w-5 h-5 text-white" />
</button>
<div className="inline-flex flex-wrap items-center gap-2.5 relative flex-[0_0_auto]">
<div className="inline-flex items-center justify-center gap-2.5 relative flex-[0_0_auto]">
<span className="relative w-fit mt-[-1.00px] font-normal text-white text-sm tracking-[0] leading-5 whitespace-nowrap">
{title}
</span>
</div>
</div>
</nav>
<div className="flex items-start gap-4 relative self-stretch w-full flex-[0_0_auto]">
<h1 className="relative w-fit mt-[-1.00px] font-semibold text-white text-2xl tracking-[0] leading-8 whitespace-nowrap">
{title}
</h1>
</div>
</header>
{/* Галерея объектов */}
{objects.length > 0 ? (
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-[18px] w-full">
{objects.map((object) => (
<ObjectCard
key={object.object_id}
object={object}
onSelect={handleObjectSelect}
isSelected={selectedObjectId === object.object_id}
/>
))}
</section>
) : (
<div className="flex items-center justify-center h-64 w-full">
<div className="text-center">
<div className="text-[#71717a] mb-2">
<svg className="w-12 h-12 mx-auto" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
</svg>
</div>
<h3 className="text-lg font-medium text-white mb-2">Объекты не найдены</h3>
<p className="text-[#71717a]">Нет доступных объектов</p>
</div>
</div>
)}
</div>
</main>
</div>
)
}
export default ObjectGallery
export type { ObjectGalleryProps }