You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
681 lines
39 KiB
681 lines
39 KiB
import React from 'react'; |
|
import { I } from '../icons.jsx'; |
|
import { CLINIC_DATA } from '../data.js'; |
|
import { Avatar, AppointmentCard, SectionHeader } from '../components.jsx'; |
|
|
|
export function HomeCardsScreen({ nav }) { |
|
const { doctors, appointments, specializations, clinic, articles } = CLINIC_DATA; |
|
const upcoming = appointments.find(a => a.status === 'upcoming'); |
|
const upDoc = upcoming && doctors.find(d => d.id === upcoming.doctor); |
|
return ( |
|
<div style={{ paddingBottom: 100 }}> |
|
<div style={{ padding: '8px 20px 16px', background: 'linear-gradient(180deg,#F5EDDF 0%,transparent 100%)' }}> |
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 20 }}> |
|
<div> |
|
<div className="sub" style={{ marginBottom: 2 }}>Добрый день,</div> |
|
<div style={{ fontSize: 22, fontWeight: 700, color: 'var(--c-fg-1)' }}>Анна Сергеевна</div> |
|
</div> |
|
<div style={{ display: 'flex', gap: 8 }}> |
|
<button onClick={() => nav.push('notifications')} className="press" style={{ width: 42, height: 42, borderRadius: 999, background: '#fff', border: '1px solid var(--c-border)', display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}> |
|
<I.bell size={20} style={{ color: 'var(--c-fg-1)' }} /> |
|
<span style={{ position: 'absolute', top: 7, right: 9, width: 8, height: 8, borderRadius: 999, background: 'var(--c-accent)' }} /> |
|
</button> |
|
<button onClick={() => nav.push('qr')} className="press" style={{ width: 42, height: 42, borderRadius: 999, background: '#fff', border: '1px solid var(--c-border)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.qr size={20} style={{ color: 'var(--c-fg-1)' }} /> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<button onClick={() => nav.push('booking-specs')} className="press" style={{ |
|
width: '100%', textAlign: 'left', |
|
background: 'var(--c-primary-darker)', color: '#fff', |
|
borderRadius: 20, padding: 18, |
|
display: 'flex', alignItems: 'center', gap: 14, |
|
boxShadow: '0 10px 30px rgba(22,107,99,.25)', |
|
}}> |
|
<div style={{ width: 48, height: 48, borderRadius: 14, background: 'rgba(255,255,255,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.plus size={26} /> |
|
</div> |
|
<div style={{ flex: 1 }}> |
|
<div style={{ fontSize: 16, fontWeight: 700, marginBottom: 2 }}>Записаться на приём</div> |
|
<div style={{ fontSize: 13, opacity: .8 }}>К ЛОР-врачу · сегодня или завтра</div> |
|
</div> |
|
<I.arrow size={22} /> |
|
</button> |
|
</div> |
|
|
|
{upcoming && upDoc && ( |
|
<div style={{ padding: '16px 20px 8px' }}> |
|
<SectionHeader title="Ближайший приём" pad="0 0 0 0" action="Все" onAction={() => nav.set('appts')} /> |
|
<AppointmentCard appt={upcoming} doctor={upDoc} addr={clinic.addresses.find(a => a.id === upcoming.address)} onClick={() => nav.push('appt:' + upcoming.id)} /> |
|
</div> |
|
)} |
|
|
|
<div style={{ padding: '16px 20px 8px' }}> |
|
<SectionHeader title="Специализации" pad="0 0 0 0" /> |
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10 }}> |
|
{specializations.map(s => { |
|
const icons = { ear: I.ear, hear: I.hearing, leaf: I.heart, mic: I.mic, baby: I.profile, scalpel: I.stetho }; |
|
const Ic = icons[s.icon]; |
|
return ( |
|
<button key={s.id} onClick={() => nav.push('doctors')} className="press card" style={{ padding: 14, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 10 }}> |
|
<div style={{ width: 36, height: 36, borderRadius: 10, background: 'var(--c-primary-100)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<Ic size={20} style={{ color: 'var(--c-primary-darker)' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 1 }}>{s.label}</div> |
|
<div className="sub" style={{ fontSize: 11 }}>{s.count} врачей</div> |
|
</div> |
|
</button> |
|
); |
|
})} |
|
</div> |
|
</div> |
|
|
|
<div style={{ padding: '16px 20px 8px' }}> |
|
<SectionHeader title="Быстрые действия" pad="0 0 0 0" /> |
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 10 }}> |
|
<button onClick={() => nav.push('telemed')} className="press card" style={{ padding: 14, display: 'flex', gap: 12, alignItems: 'center', textAlign: 'left' }}> |
|
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--c-accent-50)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.video size={20} style={{ color: 'var(--c-accent)' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700 }}>Телемедицина</div> |
|
<div className="sub" style={{ fontSize: 11 }}>Видео с врачом</div> |
|
</div> |
|
</button> |
|
<button onClick={() => nav.push('audiotest')} className="press card" style={{ padding: 14, display: 'flex', gap: 12, alignItems: 'center', textAlign: 'left' }}> |
|
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--c-primary-100)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.hearing size={20} style={{ color: 'var(--c-primary-darker)' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700 }}>Тест слуха</div> |
|
<div className="sub" style={{ fontSize: 11 }}>За 3 минуты</div> |
|
</div> |
|
</button> |
|
<button onClick={() => nav.push('results')} className="press card" style={{ padding: 14, display: 'flex', gap: 12, alignItems: 'center', textAlign: 'left' }}> |
|
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--c-warm-100)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.doc size={20} style={{ color: 'var(--c-warm-text)' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700 }}>Анализы</div> |
|
<div className="sub" style={{ fontSize: 11 }}>5 результатов</div> |
|
</div> |
|
</button> |
|
<button onClick={() => nav.push('recovery')} className="press card" style={{ padding: 14, display: 'flex', gap: 12, alignItems: 'center', textAlign: 'left' }}> |
|
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--c-success-50)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.shield size={20} style={{ color: 'var(--c-success)' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700 }}>Восстановление</div> |
|
<div className="sub" style={{ fontSize: 11 }}>День 6 из 14</div> |
|
</div> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div style={{ padding: '16px 0 8px' }}> |
|
<SectionHeader title="Статьи врачей" action="Все" onAction={() => nav.push('articles')} /> |
|
<div style={{ display: 'flex', gap: 12, overflowX: 'auto', padding: '0 20px 8px' }} className="noscroll"> |
|
{articles.map((a, i) => ( |
|
<button key={a.id} onClick={() => nav.push('article:' + a.id)} className="press card" style={{ |
|
flexShrink: 0, width: 220, padding: 0, overflow: 'hidden', textAlign: 'left', |
|
}}> |
|
<div style={{ |
|
height: 90, background: a.hero || ['#F5EDDF','#E3F4F2','#FDF8E6','#FCF1F0'][i % 4], |
|
display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', padding: 12, |
|
}}> |
|
<span className="chip chip-warm" style={{ background: 'rgba(255,255,255,0.85)' }}>{a.tag}</span> |
|
<span style={{ fontSize: 28, lineHeight: 1 }}>{a.emoji}</span> |
|
</div> |
|
<div style={{ padding: 12 }}> |
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--c-fg-1)', lineHeight: 1.3, marginBottom: 6, minHeight: 34 }}>{a.title}</div> |
|
<div className="sub" style={{ fontSize: 11 }}>{a.author} · {a.mins} мин</div> |
|
</div> |
|
</button> |
|
))} |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
export function HomeListScreen({ nav }) { |
|
const { doctors, appointments, clinic } = CLINIC_DATA; |
|
const upcoming = appointments.find(a => a.status === 'upcoming'); |
|
const upDoc = upcoming && doctors.find(d => d.id === upcoming.doctor); |
|
const items = [ |
|
{ id: 'book', icon: I.plus, title: 'Записаться на приём', sub: 'ЛОР, сурдолог, фониатр', tint: 'var(--c-primary-darker)', bg: 'var(--c-primary-100)', cta: true, go: 'booking-specs' }, |
|
{ id: 'appt', icon: I.calendar, title: 'Мои приёмы', sub: `${appointments.filter(a=>a.status==='upcoming').length} предстоящих · ${appointments.filter(a=>a.status==='past').length} прошедших`, go: 'appts' }, |
|
{ id: 'card', icon: I.file, title: 'Медицинская карта', sub: 'История, результаты, заключения', go: 'medcard' }, |
|
{ id: 'results', icon: I.doc, title: 'Анализы и обследования', sub: '5 результатов · 1 в работе', go: 'results' }, |
|
{ id: 'telemed', icon: I.video, title: 'Видеоконсультация', sub: 'Онлайн с ЛОР-врачом', go: 'telemed' }, |
|
{ id: 'audio', icon: I.hearing, title: 'Тест слуха', sub: 'Аудиограмма за 3 минуты', go: 'audiotest' }, |
|
{ id: 'recovery', icon: I.shield, title: 'Восстановление', sub: 'Септопластика · день 6', go: 'recovery' }, |
|
{ id: 'chat', icon: I.chat, title: 'Чат с врачом', sub: '2 непрочитанных', badge: 2, go: 'chat' }, |
|
]; |
|
return ( |
|
<div style={{ paddingBottom: 100 }}> |
|
<div style={{ padding: '8px 20px 16px' }}> |
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16 }}> |
|
<div> |
|
<div className="sub">Здравствуйте,</div> |
|
<div style={{ fontSize: 20, fontWeight: 700 }}>Анна Сергеевна</div> |
|
</div> |
|
<button onClick={() => nav.push('notifications')} className="press" style={{ width: 40, height: 40, borderRadius: 999, background: '#fff', border: '1px solid var(--c-border)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.bell size={18} /> |
|
</button> |
|
</div> |
|
{upcoming && upDoc && ( |
|
<div style={{ marginBottom: 18 }}> |
|
<AppointmentCard appt={upcoming} doctor={upDoc} addr={clinic.addresses.find(a=>a.id===upcoming.address)} onClick={() => nav.push('appt:' + upcoming.id)} compact /> |
|
</div> |
|
)} |
|
</div> |
|
<div className="card" style={{ margin: '0 16px', padding: 0, overflow: 'hidden' }}> |
|
{items.map((it, i) => ( |
|
<React.Fragment key={it.id}> |
|
<button onClick={() => nav.push(it.go)} className="press" style={{ |
|
width: '100%', display: 'flex', alignItems: 'center', gap: 14, |
|
padding: '14px 16px', textAlign: 'left', |
|
background: it.cta ? 'var(--c-primary-50)' : 'transparent', |
|
}}> |
|
<div style={{ |
|
width: 40, height: 40, borderRadius: 10, |
|
background: it.bg || 'var(--c-primary-100)', |
|
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0, |
|
}}> |
|
{React.createElement(it.icon, { size: 20, style: { color: it.tint || 'var(--c-primary-darker)' } })} |
|
</div> |
|
<div style={{ flex: 1, minWidth: 0 }}> |
|
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 2 }}>{it.title}</div> |
|
<div className="sub" style={{ fontSize: 12 }}>{it.sub}</div> |
|
</div> |
|
{it.badge && <span style={{ background: 'var(--c-accent)', color: '#fff', fontSize: 11, fontWeight: 700, padding: '2px 7px', borderRadius: 999 }}>{it.badge}</span>} |
|
<I.chev size={16} style={{ color: 'var(--c-fg-4)' }} /> |
|
</button> |
|
{i < items.length - 1 && <div style={{ height: 1, background: 'var(--c-divider)', marginLeft: 70 }} />} |
|
</React.Fragment> |
|
))} |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
export function HomeTimelineXScreen({ nav }) { |
|
const { doctors, appointments, clinic, articles, chronic } = CLINIC_DATA; |
|
const upcoming = appointments.find(a => a.status === 'upcoming'); |
|
const upDoc = upcoming && doctors.find(d => d.id === upcoming.doctor); |
|
const myDoctor = doctors.find(d => d.id === chronic.doctorId); |
|
|
|
const typeColors = { |
|
diagnosis: { bg: 'var(--c-primary-100)', fg: 'var(--c-primary-darker)' }, |
|
procedure: { bg: 'var(--c-primary-100)', fg: 'var(--c-primary-darker)' }, |
|
therapy: { bg: 'var(--c-warm-100)', fg: 'var(--c-warm-text)' }, |
|
flareup: { bg: 'var(--c-accent-50)', fg: 'var(--c-accent)' }, |
|
checkup: { bg: 'var(--c-success-50)', fg: 'var(--c-success)' }, |
|
}; |
|
|
|
return ( |
|
<div style={{ paddingBottom: 100 }}> |
|
{/* Greeting header */} |
|
<div style={{ padding: '8px 20px 14px' }}> |
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
|
<div> |
|
<div className="sub">20 апреля, понедельник</div> |
|
<div style={{ fontSize: 22, fontWeight: 700 }}>Здравствуйте, Анна</div> |
|
</div> |
|
<button onClick={() => nav.push('profile')} className="press"> |
|
<Avatar init="АС" size={42} /> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
{/* Health status hero */} |
|
<div style={{ padding: '0 16px 16px' }}> |
|
<div className="card" style={{ |
|
padding: 18, |
|
background: 'linear-gradient(135deg, var(--c-primary-100), var(--c-warm-100))', |
|
border: '1px solid var(--c-primary-200)', |
|
}}> |
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}> |
|
<span style={{ fontSize: 10, textTransform: 'uppercase', letterSpacing: .8, color: 'var(--c-primary-darker)', fontWeight: 700 }}>Ваше состояние</span> |
|
<span style={{ |
|
padding: '4px 10px', borderRadius: 999, |
|
background: 'rgba(255,255,255,0.75)', color: 'var(--c-success)', |
|
fontSize: 11, fontWeight: 700, display: 'inline-flex', alignItems: 'center', gap: 5, |
|
}}> |
|
<span style={{ width: 6, height: 6, borderRadius: 999, background: 'var(--c-success)' }} /> |
|
{chronic.stage} |
|
</span> |
|
</div> |
|
<div style={{ fontSize: 19, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 4 }}>{chronic.condition}</div> |
|
<div style={{ fontSize: 12, color: 'var(--c-fg-3)', marginBottom: 16 }}> |
|
Наблюдение с {chronic.diagnosed} · {myDoctor.name.split(' ').slice(0, 2).join(' ')} |
|
</div> |
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 8, paddingTop: 14, borderTop: '1px solid rgba(255,255,255,0.6)' }}> |
|
<div> |
|
<div style={{ fontFamily: 'var(--font-narrow)', fontSize: 26, fontWeight: 700, lineHeight: 1, color: 'var(--c-primary-darker)' }}>{chronic.daysSinceLastFlareup}</div> |
|
<div style={{ fontSize: 10, color: 'var(--c-fg-3)', marginTop: 3 }}>дней без обострений</div> |
|
</div> |
|
<div> |
|
<div style={{ fontFamily: 'var(--font-narrow)', fontSize: 26, fontWeight: 700, lineHeight: 1, color: 'var(--c-primary-darker)' }}>{chronic.complianceScore}%</div> |
|
<div style={{ fontSize: 10, color: 'var(--c-fg-3)', marginTop: 3 }}>комплаенс</div> |
|
</div> |
|
<div> |
|
<div style={{ fontFamily: 'var(--font-narrow)', fontSize: 26, fontWeight: 700, lineHeight: 1, color: 'var(--c-primary-darker)' }}>{chronic.flareupsThisYear}</div> |
|
<div style={{ fontSize: 10, color: 'var(--c-fg-3)', marginTop: 3 }}>обострение в году</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
{/* Current tasks */} |
|
<div style={{ padding: '0 20px 18px' }}> |
|
<SectionHeader title="Задачи сегодня" pad="0 0 8px 0" action="История" onAction={() => nav.push('medcard')} /> |
|
<div className="card" style={{ padding: 0 }}> |
|
{chronic.currentTasks.map((t, i, a) => { |
|
const isDaily = t.type === 'daily'; |
|
return ( |
|
<React.Fragment key={t.id}> |
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '12px 14px' }}> |
|
{isDaily ? ( |
|
<div style={{ |
|
width: 28, height: 28, borderRadius: 999, flexShrink: 0, |
|
background: t.done ? 'var(--c-primary-darker)' : '#fff', |
|
border: t.done ? 0 : '2px solid var(--c-border-strong)', |
|
display: 'flex', alignItems: 'center', justifyContent: 'center', |
|
}}> |
|
{t.done && <I.check size={16} style={{ color: '#fff' }} sw={3} />} |
|
</div> |
|
) : ( |
|
<div style={{ |
|
width: 28, height: 28, borderRadius: 8, flexShrink: 0, |
|
background: 'var(--c-primary-100)', |
|
display: 'flex', alignItems: 'center', justifyContent: 'center', |
|
}}> |
|
<I.calendar size={14} style={{ color: 'var(--c-primary-darker)' }} /> |
|
</div> |
|
)} |
|
<div style={{ flex: 1, minWidth: 0 }}> |
|
<div style={{ |
|
fontSize: 14, fontWeight: 600, |
|
color: isDaily && t.done ? 'var(--c-fg-3)' : 'var(--c-fg-1)', |
|
textDecoration: isDaily && t.done ? 'line-through' : 'none', |
|
}}>{t.text}</div> |
|
<div className="sub" style={{ fontSize: 11 }}> |
|
{isDaily ? (t.streak > 0 ? `Серия: ${t.streak} дней` : 'Ежедневно') : `До ${t.nextDate}`} |
|
</div> |
|
</div> |
|
{isDaily && t.streak > 0 && <span style={{ fontSize: 18 }}>🔥</span>} |
|
{!isDaily && <I.chev size={15} style={{ color: 'var(--c-fg-4)' }} />} |
|
</div> |
|
{i < a.length - 1 && <div style={{ height: 1, background: 'var(--c-divider)', marginLeft: 54 }} />} |
|
</React.Fragment> |
|
); |
|
})} |
|
</div> |
|
</div> |
|
|
|
{/* Promotion: ask AI or doctor */} |
|
<div style={{ padding: '0 20px 18px' }}> |
|
<SectionHeader title="Есть вопрос?" pad="0 0 8px 0" /> |
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}> |
|
<button onClick={() => nav.push('chat:ai')} className="press" style={{ |
|
padding: 14, borderRadius: 16, |
|
background: 'linear-gradient(135deg, var(--c-primary-darker), var(--c-primary-dark))', |
|
color: '#fff', textAlign: 'left', display: 'flex', flexDirection: 'column', gap: 6, |
|
}}> |
|
<div style={{ fontSize: 26 }}>✨</div> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 2 }}>Спросить помощника</div> |
|
<div style={{ fontSize: 11, opacity: .85, lineHeight: 1.4 }}>Быстрый ответ круглосуточно</div> |
|
</div> |
|
</button> |
|
<button onClick={() => nav.push('chat:doctor-syndaev')} className="press card" style={{ |
|
padding: 14, textAlign: 'left', display: 'flex', flexDirection: 'column', gap: 6, |
|
}}> |
|
<Avatar init={myDoctor.init} size={32} /> |
|
<div> |
|
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 2 }}>Написать врачу</div> |
|
<div className="sub" style={{ fontSize: 11, lineHeight: 1.4 }}>{myDoctor.name.split(' ').slice(0, 2).join(' ')}</div> |
|
</div> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
{/* History timeline */} |
|
<div style={{ padding: '0 20px 18px' }}> |
|
<SectionHeader title="История наблюдения" pad="0 0 8px 0" action="Вся карта" onAction={() => nav.push('medcard')} /> |
|
<div className="card" style={{ padding: '8px 0' }}> |
|
{chronic.pastVisits.map((v, i, a) => { |
|
const d = doctors.find(x => x.id === v.doctorId); |
|
const c = typeColors[v.type] || typeColors.checkup; |
|
const isLast = i === a.length - 1; |
|
return ( |
|
<div key={v.id} style={{ display: 'flex', gap: 14, padding: '10px 14px', alignItems: 'flex-start', position: 'relative' }}> |
|
{!isLast && ( |
|
<div style={{ |
|
position: 'absolute', left: 27, top: 32, bottom: -6, |
|
width: 2, background: 'var(--c-divider)', |
|
}} /> |
|
)} |
|
<div style={{ |
|
width: 28, height: 28, borderRadius: 999, flexShrink: 0, |
|
background: c.bg, color: c.fg, |
|
display: 'flex', alignItems: 'center', justifyContent: 'center', |
|
fontSize: 11, fontWeight: 700, |
|
position: 'relative', zIndex: 1, |
|
}}> |
|
{d.init} |
|
</div> |
|
<div style={{ flex: 1, paddingBottom: 4, minWidth: 0 }}> |
|
<div style={{ fontSize: 13, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 2, lineHeight: 1.35 }}>{v.title}</div> |
|
<div className="sub" style={{ fontSize: 11 }}>{v.date} · {d.name.split(' ').slice(0, 2).join(' ')}</div> |
|
</div> |
|
</div> |
|
); |
|
})} |
|
</div> |
|
</div> |
|
|
|
{/* Upcoming appointment */} |
|
{upcoming && upDoc && ( |
|
<div style={{ padding: '0 20px 18px' }}> |
|
<SectionHeader title="Ближайший приём" pad="0 0 8px 0" /> |
|
<AppointmentCard appt={upcoming} doctor={upDoc} addr={clinic.addresses.find(a => a.id === upcoming.address)} onClick={() => nav.push('appt:' + upcoming.id)} /> |
|
</div> |
|
)} |
|
|
|
{/* Recommendations */} |
|
<div style={{ padding: '0 0 18px' }}> |
|
<SectionHeader title="Рекомендации" /> |
|
<div style={{ display: 'flex', gap: 10, overflowX: 'auto', padding: '0 20px 8px' }} className="noscroll"> |
|
{chronic.recommendations.map((r, i) => ( |
|
<div key={i} className="card" style={{ |
|
flexShrink: 0, width: 170, padding: 14, |
|
}}> |
|
<div style={{ fontSize: 28, marginBottom: 8, lineHeight: 1 }}>{r.icon}</div> |
|
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 3, lineHeight: 1.3 }}>{r.title}</div> |
|
<div className="sub" style={{ fontSize: 11, lineHeight: 1.4 }}>{r.sub}</div> |
|
</div> |
|
))} |
|
</div> |
|
</div> |
|
|
|
{/* Book CTA */} |
|
<div style={{ padding: '0 20px 18px' }}> |
|
<button onClick={() => nav.push('booking-specs')} className="btn-p block"> |
|
<I.plus size={18} /> Записаться на осмотр |
|
</button> |
|
</div> |
|
|
|
{/* Articles */} |
|
<div style={{ padding: '0 0 8px' }}> |
|
<SectionHeader title="Полезное чтение" action="Все" onAction={() => nav.push('articles')} /> |
|
<div style={{ display: 'flex', gap: 12, overflowX: 'auto', padding: '0 20px 8px' }} className="noscroll"> |
|
{articles.slice(0, 3).map(a => ( |
|
<button key={a.id} onClick={() => nav.push('article:' + a.id)} className="press card" style={{ |
|
flexShrink: 0, width: 200, padding: 0, overflow: 'hidden', textAlign: 'left', |
|
}}> |
|
<div style={{ |
|
height: 80, background: a.hero, |
|
display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', padding: 10, |
|
}}> |
|
<span className="chip chip-warm" style={{ background: 'rgba(255,255,255,0.85)' }}>{a.tag}</span> |
|
<span style={{ fontSize: 24, lineHeight: 1 }}>{a.emoji}</span> |
|
</div> |
|
<div style={{ padding: 12 }}> |
|
<div style={{ fontSize: 13, fontWeight: 700, lineHeight: 1.3, marginBottom: 6, minHeight: 34 }}>{a.title}</div> |
|
<div className="sub" style={{ fontSize: 11 }}>{a.mins} мин</div> |
|
</div> |
|
</button> |
|
))} |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
export function HomeSplashScreen({ nav }) { |
|
const { patient, doctors, appointments, articles } = CLINIC_DATA; |
|
const firstName = patient.shortName.split(' ')[0]; |
|
const upcoming = appointments.find(a => a.status === 'upcoming'); |
|
const upDoc = upcoming && doctors.find(d => d.id === upcoming.doctor); |
|
const article = articles[0]; |
|
|
|
return ( |
|
<div style={{ paddingBottom: 100 }}> |
|
<div style={{ padding: '8px 20px 8px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
|
<button onClick={() => nav.push('profile')} className="press" style={{ display: 'flex', alignItems: 'center', gap: 10 }}> |
|
<div style={{ width: 34, height: 34, borderRadius: 999, background: 'var(--c-primary-200)' }} /> |
|
<span style={{ fontSize: 16, fontWeight: 600, color: 'var(--c-fg-1)' }}>Главная</span> |
|
</button> |
|
<button onClick={() => nav.push('notifications')} className="press" style={{ width: 40, height: 40, borderRadius: 999, display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.bell size={22} style={{ color: 'var(--c-primary-darker)' }} /> |
|
</button> |
|
</div> |
|
|
|
<div style={{ padding: '4px 20px 14px' }}> |
|
<h1 style={{ fontSize: 28, fontWeight: 800, lineHeight: 1.15, color: 'var(--c-fg-1)', margin: 0 }}>Добрый день, {firstName}!</h1> |
|
</div> |
|
|
|
<div style={{ padding: '0 20px 20px' }}> |
|
<button onClick={() => nav.push('search')} className="press" style={{ |
|
width: '100%', display: 'flex', alignItems: 'center', gap: 12, |
|
padding: '14px 16px', background: '#fff', |
|
border: '1px solid var(--c-border)', borderRadius: 14, |
|
textAlign: 'left', |
|
}}> |
|
<I.search size={18} style={{ color: 'var(--c-fg-3)' }} /> |
|
<span style={{ color: 'var(--c-fg-3)', fontSize: 15 }}>Поиск врача...</span> |
|
</button> |
|
</div> |
|
|
|
<div style={{ padding: '0 20px 8px' }}> |
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 10 }}>Записи на прием</div> |
|
|
|
{upcoming && upDoc && ( |
|
<button onClick={() => nav.push('appt:' + upcoming.id)} className="press" style={{ |
|
width: '100%', textAlign: 'left', |
|
background: 'var(--c-primary-50)', |
|
border: '1px solid var(--c-primary-100)', |
|
borderRadius: 16, padding: 16, marginBottom: 10, |
|
}}> |
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}> |
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, fontWeight: 700, letterSpacing: .7, color: 'var(--c-primary-darker)' }}> |
|
<span style={{ width: 6, height: 6, borderRadius: 999, background: 'var(--c-primary-darker)' }} /> |
|
БЛИЖАЙШАЯ ЗАПИСЬ |
|
</span> |
|
<span style={{ display: 'flex', alignItems: 'center', gap: 4, fontSize: 12, color: 'var(--c-primary-darker)', fontWeight: 700 }}> |
|
<I.star size={12} /> Активно |
|
</span> |
|
</div> |
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center', marginBottom: 10 }}> |
|
<Avatar init={upDoc.init} size={48} /> |
|
<div style={{ flex: 1, minWidth: 0 }}> |
|
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--c-fg-1)' }}>{upDoc.name}</div> |
|
<div style={{ fontSize: 13, color: 'var(--c-primary-darker)', marginTop: 2 }}>{upDoc.spec.split(' · ')[0]}</div> |
|
</div> |
|
</div> |
|
<div style={{ display: 'flex', gap: 8, marginBottom: 10 }}> |
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', background: '#fff', border: '1px solid var(--c-primary-100)', borderRadius: 10, fontSize: 13, fontWeight: 600 }}> |
|
<I.calendar size={14} style={{ color: 'var(--c-primary-darker)' }} /> {upcoming.date} |
|
</span> |
|
<span style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px', background: '#fff', border: '1px solid var(--c-primary-100)', borderRadius: 10, fontSize: 13, fontWeight: 600 }}> |
|
<I.clock size={14} style={{ color: 'var(--c-primary-darker)' }} /> {upcoming.time} |
|
</span> |
|
</div> |
|
<I.pin size={16} style={{ color: 'var(--c-primary-darker)', opacity: .6 }} /> |
|
</button> |
|
)} |
|
|
|
<button onClick={() => nav.set('appts')} className="press" style={{ |
|
width: '100%', textAlign: 'left', |
|
background: 'var(--c-primary-50)', |
|
border: '1px solid var(--c-primary-100)', |
|
borderRadius: 16, padding: 16, marginBottom: 10, |
|
display: 'flex', gap: 14, alignItems: 'center', |
|
}}> |
|
<div style={{ width: 54, height: 54, borderRadius: 999, background: 'var(--c-primary-darker)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}> |
|
<I.calendar size={24} style={{ color: '#fff' }} /> |
|
</div> |
|
<div style={{ flex: 1, minWidth: 0 }}> |
|
<div style={{ fontSize: 11, fontWeight: 700, letterSpacing: .7, color: 'var(--c-primary-darker)', marginBottom: 4 }}>МОИ ПРИЕМЫ</div> |
|
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 2 }}>Ближайшие приемы</div> |
|
<div style={{ fontSize: 12, color: 'var(--c-fg-3)', lineHeight: 1.35 }}>Просмотрите ваши предстоящие прием</div> |
|
</div> |
|
</button> |
|
|
|
<button onClick={() => nav.push('booking-specs')} className="press" style={{ |
|
width: '100%', textAlign: 'left', position: 'relative', overflow: 'hidden', |
|
background: 'var(--c-warm-100)', |
|
borderRadius: 16, padding: 16, marginBottom: 18, |
|
}}> |
|
<div style={{ position: 'absolute', top: -20, right: -20, width: 90, height: 90, borderRadius: 999, background: 'rgba(255,255,255,0.35)' }} /> |
|
<div style={{ position: 'absolute', top: 30, right: 30, width: 40, height: 40, borderRadius: 999, background: 'rgba(255,255,255,0.25)' }} /> |
|
<div style={{ position: 'relative', display: 'flex', gap: 14, alignItems: 'center', marginBottom: 14 }}> |
|
<div style={{ width: 48, height: 48, borderRadius: 999, background: 'var(--c-warm-text)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}> |
|
<I.calendar size={22} style={{ color: '#fff' }} /> |
|
</div> |
|
<div style={{ fontSize: 16, fontWeight: 700, color: 'var(--c-fg-1)' }}>Записаться на прием</div> |
|
</div> |
|
<div style={{ position: 'relative', padding: 12, background: '#fff', borderRadius: 12, textAlign: 'center', fontSize: 14, fontWeight: 600, color: 'var(--c-fg-1)' }}> |
|
Выбрать удобное время |
|
</div> |
|
</button> |
|
</div> |
|
|
|
<div style={{ padding: '0 20px 8px' }}> |
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 10 }}>Услуги и консультации</div> |
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 10, marginBottom: 18 }}> |
|
<SplashTile icon={I.calendar} sub="Записаться на прием" main="К врачу" onClick={() => nav.push('booking-specs')} /> |
|
<SplashTile icon={I.chat} sub="Связаться с врачом в" main="Чате" onClick={() => nav.set('chat')} /> |
|
</div> |
|
</div> |
|
|
|
<div style={{ padding: '0 20px 20px' }}> |
|
<div style={{ fontSize: 14, fontWeight: 700, color: 'var(--c-fg-1)', marginBottom: 10 }}>Полезная информация</div> |
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(2,1fr)', gap: 10 }}> |
|
<SplashTile icon={I.doc} sub="Статья" main={article.title.split(' ').slice(0,2).join(' ')} onClick={() => nav.push('article:' + article.id)} /> |
|
<SplashTile icon={I.file} sub="Все" main="Статьи" onClick={() => nav.push('articles')} /> |
|
<SplashTile icon={I.shield} sub="Информация" main="Цены" onClick={() => nav.push('prices')} /> |
|
<SplashTile icon={I.phone} sub="Информация" main="Контакты" onClick={() => nav.push('contacts')} /> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
function SplashTile({ icon: Ic, sub, main, onClick }) { |
|
return ( |
|
<button onClick={onClick} className="press" style={{ |
|
background: 'var(--c-primary-50)', |
|
border: '1px solid var(--c-primary-100)', |
|
borderRadius: 16, padding: 16, |
|
textAlign: 'left', minHeight: 120, |
|
display: 'flex', flexDirection: 'column', justifyContent: 'space-between', gap: 12, |
|
}}> |
|
<div style={{ width: 40, height: 40, borderRadius: 999, background: 'var(--c-primary-darker)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<Ic size={20} style={{ color: '#fff' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontSize: 12, color: 'var(--c-primary-darker)', marginBottom: 2 }}>{sub}</div> |
|
<div style={{ fontSize: 15, fontWeight: 700, color: 'var(--c-fg-1)' }}>{main}</div> |
|
</div> |
|
</button> |
|
); |
|
} |
|
|
|
export function HomeFeedScreen({ nav }) { |
|
const { doctors, appointments, clinic, articles, recovery } = CLINIC_DATA; |
|
const upcoming = appointments.find(a => a.status === 'upcoming'); |
|
const upDoc = upcoming && doctors.find(d => d.id === upcoming.doctor); |
|
return ( |
|
<div style={{ paddingBottom: 100 }}> |
|
<div style={{ padding: '8px 20px 12px' }}> |
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
|
<div> |
|
<div className="sub">18 апреля, суббота</div> |
|
<div style={{ fontSize: 24, fontWeight: 700 }}>Как Ваше самочувствие?</div> |
|
</div> |
|
<button onClick={() => nav.push('profile')} className="press"> |
|
<Avatar init="АС" size={42} /> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
<div style={{ padding: '0 16px' }}> |
|
<button onClick={() => nav.push('recovery')} className="press card" style={{ |
|
width: '100%', textAlign: 'left', padding: 18, marginBottom: 14, |
|
background: 'linear-gradient(135deg,#E3F4F2,#F2FAF9)', |
|
border: '1px solid var(--c-primary-200)', |
|
}}> |
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}> |
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}> |
|
<div style={{ width: 36, height: 36, borderRadius: 10, background: 'var(--c-primary-darker)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.shield size={18} style={{ color: '#fff' }} /> |
|
</div> |
|
<div> |
|
<div style={{ fontWeight: 700, fontSize: 14 }}>Восстановление</div> |
|
<div className="sub" style={{ fontSize: 12 }}>{recovery.op}</div> |
|
</div> |
|
</div> |
|
<span className="chip">День {recovery.dayNow} из {recovery.totalDays}</span> |
|
</div> |
|
<div style={{ height: 6, background: 'rgba(255,255,255,0.7)', borderRadius: 999, overflow: 'hidden', marginBottom: 12 }}> |
|
<div style={{ width: `${recovery.dayNow/recovery.totalDays*100}%`, height: '100%', background: 'var(--c-primary-darker)', borderRadius: 999 }} /> |
|
</div> |
|
<div style={{ fontSize: 13, color: 'var(--c-fg-2)' }}> |
|
Сегодня: <strong>осмотр хирурга, снятие корочек</strong> |
|
</div> |
|
</button> |
|
|
|
<div className="card" style={{ padding: 14, marginBottom: 14, display: 'flex', gap: 12, alignItems: 'center' }}> |
|
<div style={{ width: 40, height: 40, borderRadius: 10, background: 'var(--c-warning-50)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}> |
|
<I.pill size={20} style={{ color: 'var(--c-warning)' }} /> |
|
</div> |
|
<div style={{ flex: 1 }}> |
|
<div style={{ fontSize: 14, fontWeight: 700 }}>Амоксиклав 625 мг</div> |
|
<div className="sub" style={{ fontSize: 12 }}>Принять в 20:00 — через 2 часа</div> |
|
</div> |
|
<button className="btn-s" style={{ padding: '8px 12px', fontSize: 13 }}>Принял</button> |
|
</div> |
|
|
|
{upcoming && upDoc && ( |
|
<> |
|
<SectionHeader title="Ближайший приём" pad="4px 4px 8px" /> |
|
<AppointmentCard appt={upcoming} doctor={upDoc} addr={clinic.addresses.find(a=>a.id===upcoming.address)} onClick={() => nav.push('appt:' + upcoming.id)} /> |
|
</> |
|
)} |
|
|
|
<button onClick={() => nav.push('booking-specs')} className="press" style={{ |
|
width: '100%', marginTop: 14, padding: '14px 18px', borderRadius: 16, |
|
background: 'var(--c-accent)', color: '#fff', fontWeight: 700, fontSize: 15, |
|
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8, |
|
}}> |
|
<I.plus size={20} /> Записаться на приём |
|
</button> |
|
</div> |
|
|
|
<div style={{ padding: '18px 20px 8px' }}> |
|
<SectionHeader title="Полезно прочитать" action="Все" onAction={() => nav.push('articles')} pad="0 0 0 0" /> |
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}> |
|
{articles.slice(0,3).map((a)=>( |
|
<button key={a.id} onClick={() => nav.push('article:' + a.id)} className="press card" style={{ display: 'flex', gap: 12, alignItems: 'center', padding: 14, textAlign: 'left' }}> |
|
<div style={{ width: 56, height: 56, borderRadius: 12, background: a.hero, flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 28 }}> |
|
{a.emoji} |
|
</div> |
|
<div style={{ flex: 1 }}> |
|
<div style={{ fontSize: 11, color: 'var(--c-primary-dark)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: .5, marginBottom: 3 }}>{a.tag}</div> |
|
<div style={{ fontSize: 14, fontWeight: 700, lineHeight: 1.3, marginBottom: 3 }}>{a.title}</div> |
|
<div className="sub" style={{ fontSize: 12 }}>{a.author} · {a.mins} мин</div> |
|
</div> |
|
</button> |
|
))} |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
}
|
|
|