fetch sections from cms

This commit is contained in:
Zotov Ivan Yuryevich
2026-04-08 23:19:36 +03:00
parent d0ff2907cb
commit 6973e8baea
18 changed files with 518 additions and 356 deletions

View File

@@ -1,177 +1,101 @@
import {
CheckCircle,
Rocket,
Zap,
Target,
Award,
Shield,
Clock,
HeadphonesIcon,
TrendingUp,
Users,
CheckCircle2,
Cloud,
Lock,
Database,
Briefcase,
Server,
Cpu,
Package,
} from 'lucide-react'
import { useState, useEffect } from 'react'
import { toComponent } from '../lib/cms/icon'
import { toBackground, toTextColor } from '../lib/cms/color'
import type { Enum_Componentsharedcolor_Value, Enum_Componentsharedicon_Value } from '../graphql/graphql'
import type { Maybe } from '../lib/types'
const allVendors = [
'Яндекс.Облако',
'VK Cloud',
'MTC Web Services',
'Сбер Облако',
'Kaspersky',
'Positive Technologies',
'UserGate',
'Astra Linux',
'РедСофт',
'Альт Линукс',
'Postgres Pro',
'Tarantool',
'Мой Офис',
'Р7-Офис',
'1С',
'Галактика',
'Kraftway',
'Aquarius',
'Эльбрус',
'Битрикс24',
'Код Безопасности',
'Zabbix',
'Nginx',
'Selectel',
'Лаборатория Эльбрус',
'Basealt',
'МойОфис Почта',
'Видеомост',
'Jazz Telecom',
'Р-Видео',
'СКБ Контур',
'ЕТИС',
'Кодикс',
'Тензор',
'Infowatch',
'SearchInform',
'Гарда',
'Аладдин Р.Д.',
'С-Терра',
'DeviceLock',
'КриптоПро',
'Базальт СПО',
'Аскон',
'НаноСофт',
'Directum',
'Open Yard',
'ТруКонф',
'Yadro',
'Depo',
'Eltex',
'Qtech',
'D-Link',
]
export interface AboutCardCms {
id: string
title?: Maybe<string>
description?: Maybe<string>
color?: Maybe<{ value?: Maybe<Enum_Componentsharedcolor_Value> }>
icon?: Maybe<{ value?: Maybe<Enum_Componentsharedicon_Value> }>
}
export interface AboutCmsData {
title?: Maybe<string>
description?: Maybe<string>
partners?: Maybe<Array<Maybe<{ id: string; value: string }>>>
cards?: Maybe<Array<Maybe<AboutCardCms>>>
}
interface AboutProps {
data?: Maybe<AboutCmsData>
}
export function About({ data }: AboutProps) {
const title = data?.title ?? ''
const description = data?.description ?? ''
const partners = (data?.partners ?? []).filter((p): p is { id: string; value: string } => Boolean(p?.value))
const cards = (data?.cards ?? []).filter((c): c is AboutCardCms => Boolean(c))
export function About() {
const [currentVendorIndex, setCurrentVendorIndex] = useState(0)
const [isVisible, setIsVisible] = useState(true)
useEffect(() => {
if (partners.length <= 1) return
const interval = setInterval(() => {
setIsVisible(false)
setTimeout(() => {
setCurrentVendorIndex(prev => (prev + 1) % allVendors.length)
setCurrentVendorIndex(prev => (prev + 1) % partners.length)
setIsVisible(true)
}, 800)
}, 4500)
return () => clearInterval(interval)
}, [])
}, [partners.length])
if (!title && !description && partners.length === 0 && cards.length === 0) {
return null
}
return (
<section className="py-4 bg-gray-50">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">О компании</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6 flex flex-wrap items-baseline gap-2">
<span>Сотрудничаем с</span>
<span
className="text-blue-600 transition-opacity duration-700 ease-in-out whitespace-nowrap"
style={{ opacity: isVisible ? 1 : 0 }}
>
{allVendors[currentVendorIndex]}
</span>
</h2>
<p className="text-xl text-gray-600">
Комплексные решения для цифровой трансформации бизнеса. Работаем с 2021 года.
</p>
{title || partners.length > 0 ? (
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6 flex flex-wrap items-baseline gap-2">
{title ? (
<span>
{title}
{partners.length > 0 ? '\u00a0' : ''}
</span>
) : null}
{partners.length > 0 ? (
<span
className="text-blue-600 transition-opacity duration-700 ease-in-out whitespace-nowrap"
style={{ opacity: isVisible ? 1 : 0 }}
>
{partners[currentVendorIndex]?.value}
</span>
) : null}
</h2>
) : null}
{description ? <p className="text-xl text-gray-600">{description}</p> : null}
</div>
{/* Ключевые преимущества для клиента */}
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all">
<div className="w-14 h-14 bg-blue-50 rounded-2xl flex items-center justify-center mb-6">
<Package className="w-7 h-7 text-blue-600" />
</div>
<h3 className="text-xl text-gray-900 mb-3">Поставка оборудования</h3>
<p className="text-gray-600 leading-relaxed">
Комплексные поставки серверов, СХД и сетевого оборудования от российских производителей.
</p>
</div>
{cards.map(card => {
const Icon = toComponent(card.icon?.value)
const wrap = toBackground(card.color?.value)
const iconText = toTextColor(card.color?.value)
<div className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all">
<div className="w-14 h-14 bg-emerald-50 rounded-2xl flex items-center justify-center mb-6">
<Award className="w-7 h-7 text-emerald-600" />
</div>
<h3 className="text-xl text-gray-900 mb-3">Официальное партнерство</h3>
<p className="text-gray-600 leading-relaxed">
Статус золотого партнера ведущих российских вендоров. Прямые контракты и поддержка.
</p>
</div>
<div className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all">
<div className="w-14 h-14 bg-violet-50 rounded-2xl flex items-center justify-center mb-6">
<CheckCircle2 className="w-7 h-7 text-violet-600" />
</div>
<h3 className="text-xl text-gray-900 mb-3">Сертифицированные специалисты</h3>
<p className="text-gray-600 leading-relaxed">
Инженеры с подтвержденной квалификацией по всем внедряемым решениям и платформам.
</p>
</div>
<div className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all">
<div className="w-14 h-14 bg-orange-50 rounded-2xl flex items-center justify-center mb-6">
<Zap className="w-7 h-7 text-orange-600" />
</div>
<h3 className="text-xl text-gray-900 mb-3">Оперативный отклик</h3>
<p className="text-gray-600 leading-relaxed">
Быстрое реагирование на запросы клиента. Четкие сроки решения задач и прозрачная коммуникация.
</p>
</div>
<div className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all">
<div className="w-14 h-14 bg-cyan-50 rounded-2xl flex items-center justify-center mb-6">
<Users className="w-7 h-7 text-cyan-600" />
</div>
<h3 className="text-xl text-gray-900 mb-3">Выделенная команда</h3>
<p className="text-gray-600 leading-relaxed">
Персональный менеджер проекта и команда сертифицированных специалистов.
</p>
</div>
<div className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all">
<div className="w-14 h-14 bg-pink-50 rounded-2xl flex items-center justify-center mb-6">
<Rocket className="w-7 h-7 text-pink-600" />
</div>
<h3 className="text-xl text-gray-900 mb-3">Быстрый старт</h3>
<p className="text-gray-600 leading-relaxed">
Запуск типовых проектов за 2 недели. Пилотное внедрение перед полным развертываием.
</p>
</div>
return (
<div
key={card.id}
className="bg-white p-8 rounded-3xl border border-gray-100 hover:border-blue-200 hover:shadow-lg transition-all"
>
<div className={`w-14 h-14 ${wrap} rounded-2xl flex items-center justify-center mb-6`}>
<Icon className={`w-7 h-7 ${iconText}`} />
</div>
{card.title ? <h3 className="text-xl text-gray-900 mb-3">{card.title}</h3> : null}
{card.description ? <p className="text-gray-600 leading-relaxed">{card.description}</p> : null}
</div>
)
})}
</div>
</div>
</section>

View File

@@ -1,85 +1,89 @@
import { ArrowRight, Cloud, Cpu, Shield } from 'lucide-react'
import { ArrowRight } from 'lucide-react'
import { toComponent } from '../lib/cms/icon'
import { toElevatedGradient, toTextColor } from '../lib/cms/color'
import type { Enum_Componentsharedcolor_Value, Enum_Componentsharedicon_Value } from '../graphql/graphql'
import type { Maybe } from '../lib/types'
const cases = [
{
icon: Cloud,
category: 'Облако',
company: 'Торговая сеть',
title: 'Миграция в Яндекс.Облако',
description: 'Перенос корпоративной инфраструктуры 500+ серверов с нулевым простоем',
results: '−40% TCO',
gradient: 'from-blue-100 to-cyan-100',
iconBg: 'bg-white',
iconColor: 'text-blue-600',
},
{
icon: Cpu,
category: 'Автоматизация',
company: 'Производственный холдинг',
title: 'Внедрение 1С:ERP',
description: 'Автоматизация производственного и финансового учета для 15 предприятий',
results: '+25% эффективность',
gradient: 'from-emerald-100 to-teal-100',
iconBg: 'bg-white',
iconColor: 'text-emerald-600',
},
{
icon: Shield,
category: 'Безопасность',
company: 'Финансовая группа',
title: 'Защита периметра Kaspersky',
description: 'Построение многоуровневой системы защиты критичной инфраструктуры',
results: '99.9% uptime',
gradient: 'from-violet-100 to-purple-100',
iconBg: 'bg-white',
iconColor: 'text-violet-600',
},
]
export interface CaseCardCms {
id: string
title?: Maybe<string>
description?: Maybe<string>
pretitle?: Maybe<string>
badge?: Maybe<string>
footnote?: Maybe<string>
color?: Maybe<{ value?: Maybe<Enum_Componentsharedcolor_Value> }>
icon?: Maybe<{ value?: Maybe<Enum_Componentsharedicon_Value> }>
}
export interface CasesCmsData {
title?: Maybe<string>
description?: Maybe<string>
cards?: Maybe<Array<Maybe<CaseCardCms>>>
}
interface CasesProps {
data?: Maybe<CasesCmsData>
}
export function Cases({ data }: CasesProps) {
const title = data?.title ?? ''
const description = data?.description ?? ''
const cards = (data?.cards ?? []).filter((c): c is CaseCardCms => Boolean(c))
if (!title && !description && cards.length === 0) {
return null
}
export function Cases() {
return (
<section className="py-4 bg-white">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Кейсы</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">Реализованные проекты</h2>
<p className="text-xl text-gray-600">Примеры успешного внедрения с конкретными результатами</p>
{title ? <h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">{title}</h2> : null}
{description ? <p className="text-xl text-gray-600">{description}</p> : null}
</div>
<div className="grid lg:grid-cols-3 gap-8">
{cases.map((caseItem, index) => {
const Icon = caseItem.icon
{cards.map((caseItem, index) => {
const Icon = toComponent(caseItem.icon?.value)
const gradient = toElevatedGradient(caseItem.color?.value)
const iconColorClass = toTextColor(caseItem.color?.value)
const floatClass =
index === 0 ? 'animate-float' : index === 1 ? 'animate-float-delay-1' : 'animate-float-delay-2'
return (
<div
key={index}
key={caseItem.id}
className="group bg-white rounded-2xl overflow-hidden border border-gray-100 hover:border-blue-200 hover:shadow-xl transition-all duration-300 cursor-pointer flex flex-col"
>
<div
className={`relative h-64 overflow-hidden bg-linear-to-br ${caseItem.gradient} flex items-center justify-center`}
className={`relative h-64 overflow-hidden bg-linear-to-br ${gradient} flex items-center justify-center`}
>
<div
className={`w-24 h-24 ${caseItem.iconBg} rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-md ${floatClass}`}
className={`w-24 h-24 bg-white rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-md ${floatClass}`}
>
<Icon className={`w-12 h-12 ${caseItem.iconColor}`} />
</div>
<div className="absolute top-4 left-4">
<span className="px-3 py-1 bg-white/90 backdrop-blur-sm text-gray-900 rounded-full border border-gray-200 text-sm">
{caseItem.category}
</span>
<Icon className={`w-12 h-12 ${iconColorClass}`} />
</div>
{caseItem.badge ? (
<div className="absolute top-4 left-4">
<span className="px-3 py-1 bg-white/90 backdrop-blur-sm text-gray-900 rounded-full border border-gray-200 text-sm">
{caseItem.badge}
</span>
</div>
) : null}
</div>
<div className="p-6 flex flex-col flex-1">
<div className="text-sm text-blue-600 mb-2">{caseItem.company}</div>
{caseItem.pretitle ? <div className="text-sm text-blue-600 mb-2">{caseItem.pretitle}</div> : null}
<h3 className="text-xl text-gray-900 mb-3">{caseItem.title}</h3>
{caseItem.title ? <h3 className="text-xl text-gray-900 mb-3">{caseItem.title}</h3> : null}
<p className="text-gray-600 mb-4 leading-relaxed flex-1">{caseItem.description}</p>
{caseItem.description ? (
<p className="text-gray-600 mb-4 leading-relaxed flex-1">{caseItem.description}</p>
) : null}
<div className="pt-4 border-t border-gray-100 flex items-center justify-between">
<span className="text-gray-900">{caseItem.results}</span>
<span className="text-gray-900">{caseItem.footnote ?? ''}</span>
<ArrowRight className="w-5 h-5 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-1 transition-all" />
</div>
</div>
@@ -89,7 +93,10 @@ export function Cases() {
</div>
<div className="text-center mt-12">
<button className="px-8 py-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all shadow-md hover:shadow-lg inline-flex items-center gap-2">
<button
type="button"
className="px-8 py-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all shadow-md hover:shadow-lg inline-flex items-center gap-2"
>
Все кейсы
<ArrowRight className="w-5 h-5" />
</button>

View File

@@ -1,58 +1,70 @@
import { ArrowRight, Play, Cloud, Shield, Zap, Database, Code, Users } from 'lucide-react'
import { ArrowRight, Cloud, Shield, Zap, Database, Code, Users } from 'lucide-react'
import type { Maybe } from '../lib/types'
export interface HeroCmsData {
badge?: Maybe<string>
title?: Maybe<string>
description?: Maybe<string>
stats?: Maybe<Array<Maybe<{ id: string; key?: Maybe<string>; value?: Maybe<string> }>>>
}
interface HeroProps {
onOpenContactModal?: () => void
data?: Maybe<HeroCmsData>
}
export function Hero({ onOpenContactModal }: HeroProps) {
export function Hero({ onOpenContactModal, data }: HeroProps) {
const badge = data?.badge ?? ''
const title = data?.title ?? ''
const description = data?.description ?? ''
const stats = (data?.stats ?? []).filter(Boolean) as Array<{ id: string; key?: Maybe<string>; value?: Maybe<string> }>
return (
<section className="relative pt-32 pb-20 overflow-hidden bg-white">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="grid lg:grid-cols-2 gap-16 items-center">
<div className="space-y-8">
<div className="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 rounded-full border border-blue-100">
<span className="w-2 h-2 bg-blue-600 rounded-full animate-pulse"></span>
<span>Надежный партнер с 2021 года</span>
</div>
{badge ? (
<div className="inline-flex items-center gap-2 px-4 py-2 bg-blue-50 text-blue-700 rounded-full border border-blue-100">
<span className="w-2 h-2 bg-blue-600 rounded-full animate-pulse"></span>
<span>{badge}</span>
</div>
) : null}
<h1 className="text-5xl lg:text-6xl text-gray-900 leading-tight">IT-решения для роста вашего бизнеса</h1>
{title ? <h1 className="text-5xl lg:text-6xl text-gray-900 leading-tight">{title}</h1> : null}
<p className="text-xl text-gray-600 leading-relaxed">
Проектируем, внедряем и сопровождаем корпоративные IT-системы. Облачная инфраструктура, безопасность,
автоматизация процессов.
</p>
{description ? <p className="text-xl text-gray-600 leading-relaxed">{description}</p> : null}
<div className="flex flex-wrap gap-4">
<button
className="group flex items-center gap-2 px-8 py-4 bg-blue-600 text-white rounded-xl hover:bg-blue-700 transition-all"
onClick={onOpenContactModal}
type="button"
>
Обсудить проект
<ArrowRight className="w-5 h-5 group-hover:translate-x-1 transition-transform" />
</button>
<button className="flex items-center gap-2 px-8 py-4 bg-gray-50 text-gray-900 rounded-xl hover:bg-gray-100 transition-all">
<a
href="#cases"
className="flex items-center gap-2 px-8 py-4 bg-gray-50 text-gray-900 rounded-xl hover:bg-gray-100 transition-all"
>
Портфолио
</button>
</a>
</div>
<div className="grid grid-cols-3 gap-8 pt-8 border-t border-gray-100">
<div>
<div className="text-3xl text-gray-900 mb-1">300+</div>
<div className="text-gray-600">клиентов</div>
{stats.length > 0 ? (
<div className="grid grid-cols-3 gap-8 pt-8 border-t border-gray-100">
{stats.map(stat => (
<div key={stat.id}>
<div className="text-3xl text-gray-900 mb-1">{stat.value ?? ''}</div>
<div className="text-gray-600">{stat.key ?? ''}</div>
</div>
))}
</div>
<div>
<div className="text-3xl text-gray-900 mb-1">50+</div>
<div className="text-gray-600">партнерств</div>
</div>
<div>
<div className="text-3xl text-gray-900 mb-1">95%</div>
<div className="text-gray-600">повторных обращений</div>
</div>
</div>
) : null}
</div>
{/* Простая сетка иконок в стиле VK/Яндекс */}
<div className="relative">
<style>{`
@keyframes gentleFloat {

View File

@@ -1,63 +1,50 @@
import { Cloud, Shield, Cpu, Database, Server, Settings } from 'lucide-react'
import { toComponent } from '../lib/cms/icon'
import { toGradient, toTextColor } from '../lib/cms/color'
import type { Enum_Componentsharedcolor_Value, Enum_Componentsharedicon_Value } from '../graphql/graphql'
import type { Maybe } from '../lib/types'
const services = [
{
icon: Cloud,
title: 'Облачные решения',
description: 'Миграция и управление инфраструктурой на базе Яндекс.Облако и VK Cloud',
gradient: 'from-blue-50 to-cyan-50',
iconColor: 'text-blue-600',
},
{
icon: Shield,
title: 'Кибербезопасность',
description: 'Комплексная защита периметра и данных с решениями Kaspersky',
gradient: 'from-violet-50 to-purple-50',
iconColor: 'text-violet-600',
},
{
icon: Cpu,
title: 'Цифровая трансформация',
description: 'Автоматизация процессов на платформах 1С и Галактика',
gradient: 'from-emerald-50 to-teal-50',
iconColor: 'text-emerald-600',
},
{
icon: Database,
title: 'Управление данными',
description: 'Развертывание и поддержка СУБД Postgres Pro',
gradient: 'from-orange-50 to-amber-50',
iconColor: 'text-orange-600',
},
{
icon: Server,
title: 'Серверная инфраструктура',
description: 'Поставка и настройка оборудования Kraftway',
gradient: 'from-pink-50 to-rose-50',
iconColor: 'text-pink-600',
},
{
icon: Settings,
title: 'Системная интеграция',
description: 'Построение корпоративной ИТ-архитектуры на Astra Linux',
gradient: 'from-indigo-50 to-blue-50',
iconColor: 'text-indigo-600',
},
]
export interface ServiceCardCms {
id: string
title?: Maybe<string>
description?: Maybe<string>
icon?: Maybe<{ value?: Maybe<Enum_Componentsharedicon_Value> }>
color?: Maybe<{ value?: Maybe<Enum_Componentsharedcolor_Value> }>
category?: Maybe<{ slug: string }>
}
export interface ServicesCmsData {
title?: Maybe<string>
description?: Maybe<string>
cards?: Maybe<Array<Maybe<ServiceCardCms>>>
}
interface ServicesProps {
data?: Maybe<ServicesCmsData>
}
export function Services({ data }: ServicesProps) {
const title = data?.title ?? ''
const description = data?.description ?? ''
const cards = (data?.cards ?? []).filter((c): c is ServiceCardCms => Boolean(c))
if (!title && !description && cards.length === 0) {
return null
}
export function Services() {
return (
<section id="services" className="py-24 bg-white">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Услуги</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">Комплексные IT-решения для вашего бизнеса</h2>
<p className="text-xl text-gray-600">Полный цикл: от стратегии до технической поддержки</p>
{title ? <h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">{title}</h2> : null}
{description ? <p className="text-xl text-gray-600">{description}</p> : null}
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{services.map((service, index) => {
const Icon = service.icon
{cards.map((service, index) => {
const Icon = toComponent(service.icon?.value)
const gradient = toGradient(service.color?.value)
const iconColor = toTextColor(service.color?.value)
const floatClasses = [
'animate-float',
'animate-float-delay-1',
@@ -67,31 +54,44 @@ export function Services() {
'animate-float-delay-5',
]
const floatClass = floatClasses[index % 6]
const slug = service.category?.slug
return (
<div
key={index}
key={service.id}
className="group bg-white rounded-2xl border border-gray-100 hover:border-blue-200 hover:shadow-xl transition-all duration-300 overflow-hidden"
>
<div
className={`relative h-48 overflow-hidden bg-linear-to-br ${service.gradient} flex items-center justify-center`}
className={`relative h-48 overflow-hidden bg-linear-to-br ${gradient} flex items-center justify-center`}
>
<div
className={`w-20 h-20 bg-white rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-md ${floatClass}`}
>
<Icon className={`w-10 h-10 ${service.iconColor}`} />
<Icon className={`w-10 h-10 ${iconColor}`} />
</div>
</div>
<div className="p-6">
<h3 className="text-xl text-gray-900 mb-3">{service.title}</h3>
{service.title ? <h3 className="text-xl text-gray-900 mb-3">{service.title}</h3> : null}
<p className="text-gray-600 mb-4 leading-relaxed">{service.description}</p>
{service.description ? (
<p className="text-gray-600 mb-4 leading-relaxed">{service.description}</p>
) : null}
<button className="text-blue-600 hover:text-blue-700 transition-colors inline-flex items-center gap-2">
Подробнее
<span className="transition-all">&rarr;</span>
</button>
{slug ? (
<a
href={`/${slug}`}
className="text-blue-600 hover:text-blue-700 transition-colors inline-flex items-center gap-2"
>
Подробнее
<span className="transition-all">&rarr;</span>
</a>
) : (
<span className="text-blue-600 inline-flex items-center gap-2">
Подробнее
<span className="transition-all">&rarr;</span>
</span>
)}
</div>
</div>
)

View File

@@ -1,75 +1,83 @@
import { Building2, ShoppingCart, Factory, Heart, GraduationCap, Landmark } from 'lucide-react'
import { toComponent } from '../lib/cms/icon'
import type { Enum_Componentsharedicon_Value } from '../graphql/graphql'
import type { Maybe } from '../lib/types'
const solutions = [
{
icon: Building2,
title: 'Корпоративный сектор',
description: 'ERP, CRM, автоматизация документооборота',
projects: '1200+ проектов',
},
{
icon: ShoppingCart,
title: 'Ритейл и e-commerce',
description: 'Омниканальные платформы продаж',
projects: '850+ проектов',
},
{
icon: Factory,
title: 'Производство',
description: 'IoT, системы управления производством',
projects: '650+ проектов',
},
{
icon: Heart,
title: 'Здравоохранение',
description: 'Медицинские информационные системы',
projects: '420+ проектов',
},
{
icon: GraduationCap,
title: 'Образование',
description: 'Платформы дистанционного обучения',
projects: '380+ проектов',
},
{
icon: Landmark,
title: 'Государственный сектор',
description: 'Цифровизация госуслуг',
projects: '500+ проектов',
},
]
export interface SolutionCardCms {
id: string
title?: Maybe<string>
description?: Maybe<string>
footnote?: Maybe<string>
icon?: Maybe<{ value?: Maybe<Enum_Componentsharedicon_Value> }>
category?: Maybe<{ slug: string }>
}
export interface SolutionsCmsData {
title?: Maybe<string>
description?: Maybe<string>
cards?: Maybe<Array<Maybe<SolutionCardCms>>>
}
interface SolutionsProps {
data?: Maybe<SolutionsCmsData>
}
export function Solutions({ data }: SolutionsProps) {
const title = data?.title ?? ''
const description = data?.description ?? ''
const cards = (data?.cards ?? []).filter((c): c is SolutionCardCms => Boolean(c))
if (!title && !description && cards.length === 0) {
return null
}
export function Solutions() {
return (
<section id="solutions" className="py-24 bg-gray-50">
<div className="max-w-400 mx-auto px-6 xl:px-12 2xl:px-16">
<div className="max-w-3xl mb-16">
<p className="text-sm uppercase tracking-wider text-gray-500 mb-4">Отраслевые решения</p>
<h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">Решения под ключ для разных отраслей</h2>
<p className="text-xl text-gray-600">Глубокое понимание специфики бизнеса в каждой отрасли</p>
{title ? <h2 className="text-4xl lg:text-5xl text-gray-900 mb-6">{title}</h2> : null}
{description ? <p className="text-xl text-gray-600">{description}</p> : null}
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{solutions.map((solution, index) => {
const Icon = solution.icon
return (
<div
key={index}
className="group p-8 bg-white rounded-2xl hover:shadow-xl border border-gray-100 hover:border-blue-100 transition-all duration-300 cursor-pointer"
>
{cards.map(solution => {
const Icon = toComponent(solution.icon?.value)
const slug = solution.category?.slug
const inner = (
<>
<div className="flex items-start gap-4 mb-4">
<div className="w-12 h-12 bg-linear-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center group-hover:scale-110 transition-transform shadow-sm">
<Icon className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<h3 className="text-lg text-gray-900 mb-2">{solution.title}</h3>
<p className="text-gray-600">{solution.description}</p>
{solution.title ? <h3 className="text-lg text-gray-900 mb-2">{solution.title}</h3> : null}
{solution.description ? <p className="text-gray-600">{solution.description}</p> : null}
</div>
</div>
<div className="pt-4 border-t border-gray-100">
<span className="text-sm text-blue-600">{solution.projects}</span>
</div>
{solution.footnote ? (
<div className="pt-4 border-t border-gray-100">
<span className="text-sm text-blue-600">{solution.footnote}</span>
</div>
) : null}
</>
)
return slug ? (
<a
key={solution.id}
href={`/${slug}`}
className="group p-8 bg-white rounded-2xl hover:shadow-xl border border-gray-100 hover:border-blue-100 transition-all duration-300 cursor-pointer block"
>
{inner}
</a>
) : (
<div
key={solution.id}
className="group p-8 bg-white rounded-2xl hover:shadow-xl border border-gray-100 hover:border-blue-100 transition-all duration-300"
>
{inner}
</div>
)
})}