Initial scaffold: Клиника УГН mobile prototype
Ported HTML/CSS/JS design bundle from Claude Design to Vite + React. 20 screens (home 3 variants, booking flow, doctors, appointments, results + audiogram, recovery, audio test, chat, profile, QR, telemed, medcard, notifications). Tweaks panel with iOS/Android frames, layout/accent/font switchers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,450 @@
|
||||
import React, { useState } from 'react';
|
||||
import { I } from '../icons.jsx';
|
||||
import { CLINIC_DATA } from '../data.js';
|
||||
import { Avatar, DoctorCard, ScreenHeader } from '../components.jsx';
|
||||
|
||||
export function BookingSpecsScreen({ nav }) {
|
||||
const { specializations } = CLINIC_DATA;
|
||||
return (
|
||||
<div style={{ paddingBottom: 100 }}>
|
||||
<ScreenHeader title="Запись на приём" subtitle="Шаг 1 из 4" onBack={() => nav.pop()} />
|
||||
<div style={{ padding: '8px 20px 12px' }}>
|
||||
<div className="h-screen" style={{ marginBottom: 8 }}>Выберите направление</div>
|
||||
<div className="sub" style={{ marginBottom: 16 }}>27 ЛОР-врачей в клинике · 6 кандидатов мед. наук</div>
|
||||
|
||||
<div className="seg" style={{ marginBottom: 18 }}>
|
||||
{['Взрослому', 'Ребёнку', 'Онлайн'].map((l, i) => (
|
||||
<button key={i} className={i === 0 ? 'on' : ''}>{l}</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 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('booking-doctor:' + s.id)} className="press card" style={{
|
||||
padding: 16, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 12,
|
||||
textAlign: 'left',
|
||||
}}>
|
||||
<div style={{ width: 44, height: 44, borderRadius: 12, background: 'var(--c-primary-100)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Ic size={24} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 15, fontWeight: 700, marginBottom: 2 }}>{s.label}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{s.count} специалистов</div>
|
||||
</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--c-primary-darker)', fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
от 1500 ₽ <I.chev size={14} />
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BookingDoctorScreen({ nav }) {
|
||||
const { doctors } = CLINIC_DATA;
|
||||
const [q, setQ] = useState('');
|
||||
const filtered = doctors.filter(d => !q || d.name.toLowerCase().includes(q.toLowerCase()) || d.spec.toLowerCase().includes(q.toLowerCase()));
|
||||
return (
|
||||
<div style={{ paddingBottom: 100 }}>
|
||||
<ScreenHeader title="Выберите врача" subtitle="Шаг 2 из 4" onBack={() => nav.pop()} rightIcon={I.filter} />
|
||||
<div style={{ padding: '0 16px 12px' }}>
|
||||
<div style={{
|
||||
background: '#fff', borderRadius: 14, padding: '10px 14px',
|
||||
display: 'flex', alignItems: 'center', gap: 10,
|
||||
border: '1px solid var(--c-border)',
|
||||
}}>
|
||||
<I.search size={18} style={{ color: 'var(--c-fg-4)' }} />
|
||||
<input value={q} onChange={e => setQ(e.target.value)} placeholder="ФИО или специализация" style={{
|
||||
flex: 1, border: 0, outline: 0, fontSize: 15, background: 'transparent',
|
||||
}} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="pills" style={{ marginBottom: 12 }}>
|
||||
{['Все','Свободно сегодня','Кандидаты наук','Хирурги','Детские'].map((p,i)=>(
|
||||
<button key={i} className={'pill' + (i===0?' on':'')}>{p}</button>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ padding: '0 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{filtered.map(d => (
|
||||
<DoctorCard key={d.id} doc={d} variant="rich" onClick={() => nav.push('booking-time:' + d.id)} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BookingTimeScreen({ nav, doctorId }) {
|
||||
const doc = CLINIC_DATA.doctors.find(d => d.id === doctorId);
|
||||
const [selDate, setSelDate] = useState(1);
|
||||
const [selTime, setSelTime] = useState('16:00');
|
||||
|
||||
const dates = [
|
||||
{ i: 0, d: 'Сегодня', n: '20', wd: 'вс' },
|
||||
{ i: 1, d: 'Пн', n: '21', wd: 'пн' },
|
||||
{ i: 2, d: 'Вт', n: '22', wd: 'вт' },
|
||||
{ i: 3, d: 'Ср', n: '23', wd: 'ср' },
|
||||
{ i: 4, d: 'Чт', n: '24', wd: 'чт' },
|
||||
{ i: 5, d: 'Пт', n: '25', wd: 'пт' },
|
||||
{ i: 6, d: 'Сб', n: '26', wd: 'сб' },
|
||||
];
|
||||
const slots = {
|
||||
morning: ['09:00','09:30','10:00','10:30','11:15','11:45'],
|
||||
day: ['12:00','13:30','14:00','14:30','15:15','15:45'],
|
||||
evening: ['16:00','16:30','17:15','18:00','18:30','19:15','20:00'],
|
||||
};
|
||||
const booked = new Set(['10:30','14:00','18:00']);
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 130 }}>
|
||||
<ScreenHeader title="Дата и время" subtitle="Шаг 3 из 4" onBack={() => nav.pop()} />
|
||||
|
||||
<div style={{ padding: '0 20px 16px' }}>
|
||||
<div className="card" style={{ display: 'flex', gap: 12, alignItems: 'center', padding: 14 }}>
|
||||
<Avatar init={doc.init} size={48} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 700 }}>{doc.name.split(' ').slice(0,2).join(' ')}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{doc.spec}</div>
|
||||
</div>
|
||||
<div className="price" style={{ fontSize: 16 }}>{doc.price} ₽</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<div style={{ padding: '0 20px 10px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<h2 className="h-sec">Апрель 2026</h2>
|
||||
<button style={{ color: 'var(--c-primary-darker)', fontSize: 13, fontWeight: 700, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<I.calendar size={15} /> Календарь
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, padding: '0 16px', overflowX: 'auto' }} className="noscroll">
|
||||
{dates.map(d => (
|
||||
<button key={d.i} onClick={() => setSelDate(d.i)} className="press" style={{
|
||||
flexShrink: 0, padding: '10px 4px', width: 54, borderRadius: 14,
|
||||
background: selDate === d.i ? 'var(--c-primary-darker)' : '#fff',
|
||||
color: selDate === d.i ? '#fff' : 'var(--c-fg-1)',
|
||||
border: selDate === d.i ? 0 : '1px solid var(--c-border)',
|
||||
textAlign: 'center',
|
||||
}}>
|
||||
<div style={{ fontSize: 11, opacity: .7, marginBottom: 3 }}>{d.d}</div>
|
||||
<div style={{ fontFamily: 'var(--font-narrow)', fontSize: 20, fontWeight: 700, lineHeight: 1 }}>{d.n}</div>
|
||||
<div style={{ fontSize: 10, marginTop: 4, opacity: selDate===d.i ? .7 : .5 }}>
|
||||
{selDate===d.i ? '12 окон' : '·'}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
{Object.entries({ 'Утро': slots.morning, 'День': slots.day, 'Вечер': slots.evening }).map(([label, arr]) => (
|
||||
<div key={label} style={{ marginBottom: 18 }}>
|
||||
<div style={{ fontSize: 12, fontWeight: 700, color: 'var(--c-fg-3)', textTransform: 'uppercase', letterSpacing: .5, marginBottom: 10 }}>{label}</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4,1fr)', gap: 8 }}>
|
||||
{arr.map(t => {
|
||||
const isBooked = booked.has(t);
|
||||
const sel = selTime === t;
|
||||
return (
|
||||
<button key={t} disabled={isBooked} onClick={() => setSelTime(t)} className="press" style={{
|
||||
padding: '11px 0', borderRadius: 10, fontSize: 14, fontWeight: 700,
|
||||
background: sel ? 'var(--c-primary-darker)' : isBooked ? 'var(--c-divider)' : '#fff',
|
||||
color: sel ? '#fff' : isBooked ? 'var(--c-fg-4)' : 'var(--c-fg-1)',
|
||||
border: sel ? 0 : '1px solid var(--c-border)',
|
||||
textDecoration: isBooked ? 'line-through' : 'none',
|
||||
cursor: isBooked ? 'not-allowed' : 'pointer',
|
||||
}}>{t}</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
position: 'absolute', left: 0, right: 0, bottom: 0, padding: '14px 20px 34px',
|
||||
background: 'rgba(255,255,255,0.95)', backdropFilter: 'blur(10px)',
|
||||
borderTop: '1px solid var(--c-border)',
|
||||
}}>
|
||||
<button onClick={() => nav.push('booking-confirm:' + doctorId + ':' + selDate + ':' + selTime)} className="btn-p block">
|
||||
Выбрать · {dates[selDate].d}, {selTime}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BookingConfirmScreen({ nav, doctorId, dateIdx, time }) {
|
||||
const doc = CLINIC_DATA.doctors.find(d => d.id === doctorId);
|
||||
const addr = CLINIC_DATA.clinic.addresses.find(a => a.id === doc.address);
|
||||
const [type, setType] = useState('offline');
|
||||
const [comment, setComment] = useState('');
|
||||
const dates = ['Сегодня, 20 апр','Пн, 21 апр','Вт, 22 апр','Ср, 23 апр','Чт, 24 апр','Пт, 25 апр','Сб, 26 апр'];
|
||||
return (
|
||||
<div style={{ paddingBottom: 120 }}>
|
||||
<ScreenHeader title="Подтверждение" subtitle="Шаг 4 из 4" onBack={() => nav.pop()} />
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
<div className="card" style={{ padding: 0, overflow: 'hidden', marginBottom: 16 }}>
|
||||
<div style={{ padding: 16, display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||
<Avatar init={doc.init} size={50} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: 15 }}>{doc.name.split(' ').slice(0,2).join(' ')}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{doc.spec}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
<div style={{ padding: '12px 16px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span className="sub">Дата</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700 }}>{dates[dateIdx]}</span>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
<div style={{ padding: '12px 16px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span className="sub">Время</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700 }}>{time}</span>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
<div style={{ padding: '12px 16px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<span className="sub">Адрес</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700, textAlign: 'right' }}>{addr.full}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-sec" style={{ marginBottom: 10 }}>Формат приёма</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 20 }}>
|
||||
{[
|
||||
{ id: 'offline', lb: 'Очно', sub: 'В клинике', i: I.pin },
|
||||
{ id: 'online', lb: 'Онлайн', sub: 'Видеосвязь', i: I.video },
|
||||
].map(o => {
|
||||
const OIcon = o.i;
|
||||
return (
|
||||
<button key={o.id} onClick={() => setType(o.id)} className="press" style={{
|
||||
padding: 14, borderRadius: 14,
|
||||
background: type === o.id ? 'var(--c-primary-100)' : '#fff',
|
||||
border: `2px solid ${type === o.id ? 'var(--c-primary-darker)' : 'var(--c-border)'}`,
|
||||
display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 8, textAlign: 'left',
|
||||
}}>
|
||||
<OIcon size={22} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
<div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700 }}>{o.lb}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{o.sub}</div>
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="h-sec" style={{ marginBottom: 10 }}>Комментарий для врача</div>
|
||||
<textarea value={comment} onChange={e => setComment(e.target.value)} placeholder="Опишите симптомы или задайте вопрос" style={{
|
||||
width: '100%', padding: 14, borderRadius: 14, border: '1px solid var(--c-border)',
|
||||
fontSize: 14, resize: 'none', minHeight: 80, background: '#fff', outline: 'none',
|
||||
}} />
|
||||
|
||||
<div style={{ marginTop: 20, display: 'flex', alignItems: 'center', gap: 10, padding: 14, background: 'var(--c-warm-50)', borderRadius: 12 }}>
|
||||
<I.shield size={20} style={{ color: 'var(--c-warm-text)' }} />
|
||||
<div className="sub" style={{ fontSize: 12 }}>Приём можно отменить или перенести не позднее чем за 3 часа</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '14px 20px 34px', background: '#fff', borderTop: '1px solid var(--c-border)' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 12 }}>
|
||||
<span className="sub">К оплате в клинике</span>
|
||||
<span className="price" style={{ fontSize: 20 }}>{doc.price} ₽</span>
|
||||
</div>
|
||||
<button onClick={() => nav.push('booking-success')} className="btn-p block">Подтвердить запись</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function BookingSuccessScreen({ nav }) {
|
||||
return (
|
||||
<div style={{ padding: '0 20px', height: '100%', display: 'flex', flexDirection: 'column', position: 'relative' }}>
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: '20px 0' }}>
|
||||
<div style={{
|
||||
width: 96, height: 96, borderRadius: 999,
|
||||
background: 'var(--c-primary-100)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
marginBottom: 24, position: 'relative',
|
||||
}}>
|
||||
<div style={{
|
||||
position: 'absolute', inset: -12, borderRadius: 999,
|
||||
background: 'var(--c-primary-100)', opacity: .4,
|
||||
}} />
|
||||
<I.check size={56} style={{ color: 'var(--c-primary-darker)', position: 'relative' }} sw={3} />
|
||||
</div>
|
||||
<h1 style={{ fontSize: 24, fontWeight: 700, margin: '0 0 8px', color: 'var(--c-fg-1)' }}>Вы записаны!</h1>
|
||||
<p style={{ fontSize: 15, color: 'var(--c-fg-3)', margin: '0 0 28px', maxWidth: 280 }}>
|
||||
Понедельник, 21 апреля в 16:00 к Семериковой Н.А. Напомним за 2 часа.
|
||||
</p>
|
||||
<div className="card" style={{ padding: 16, width: '100%', textAlign: 'left', marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 10 }}>
|
||||
<I.pin size={18} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
<div style={{ fontSize: 14, fontWeight: 700 }}>ул. Клары Цеткин, 9</div>
|
||||
</div>
|
||||
<div className="sub" style={{ fontSize: 13, paddingLeft: 28 }}>Каб. 204 · 2 этаж</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '8px 0 24px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<button onClick={() => { nav.reset(); nav.set('appts'); }} className="btn-p block">К моим приёмам</button>
|
||||
<button onClick={() => nav.reset()} className="btn-g" style={{ width: '100%', padding: 14 }}>На главную</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DoctorsTabScreen({ nav, ctx }) {
|
||||
const { doctors } = CLINIC_DATA;
|
||||
const [q, setQ] = useState('');
|
||||
const [filter, setFilter] = useState('all');
|
||||
const filtered = doctors.filter(d => !q || d.name.toLowerCase().includes(q.toLowerCase()));
|
||||
return (
|
||||
<div style={{ paddingBottom: 100 }}>
|
||||
<div style={{ padding: '12px 20px 16px' }}>
|
||||
<h1 className="h-screen" style={{ marginBottom: 14 }}>Врачи</h1>
|
||||
<div style={{ background: '#fff', borderRadius: 14, padding: '10px 14px', display: 'flex', alignItems: 'center', gap: 10, border: '1px solid var(--c-border)' }}>
|
||||
<I.search size={18} style={{ color: 'var(--c-fg-4)' }} />
|
||||
<input value={q} onChange={e => setQ(e.target.value)} placeholder="Найти врача" style={{ flex: 1, border: 0, outline: 0, fontSize: 15, background: 'transparent' }} />
|
||||
<I.filter size={18} style={{ color: 'var(--c-fg-3)' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="pills" style={{ marginBottom: 12 }}>
|
||||
{['Все','Свободно сегодня','Кандидаты наук','Хирурги','Детские','Сурдологи'].map((p,i)=>(
|
||||
<button key={i} onClick={() => setFilter(p)} className={'pill' + (filter===p ? ' on' : (i===0 && filter==='all' ? ' on' : ''))}>{p}</button>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ padding: '0 16px' }}>
|
||||
{ctx.docVariant === 'photo' ? (
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
||||
{filtered.map(d => <DoctorCard key={d.id} doc={d} variant="photo" onClick={() => nav.push('doctor:' + d.id)} />)}
|
||||
</div>
|
||||
) : ctx.docVariant === 'list' ? (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{filtered.map(d => <DoctorCard key={d.id} doc={d} variant="list" onClick={() => nav.push('doctor:' + d.id)} dense={ctx.density==='compact'} />)}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{filtered.map(d => <DoctorCard key={d.id} doc={d} variant="rich" onClick={() => nav.push('doctor:' + d.id)} dense={ctx.density==='compact'} />)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function DoctorDetailScreen({ nav, doctorId }) {
|
||||
const doc = CLINIC_DATA.doctors.find(d => d.id === doctorId);
|
||||
const [tab, setTab] = useState('info');
|
||||
return (
|
||||
<div style={{ paddingBottom: 110 }}>
|
||||
<ScreenHeader title="" onBack={() => nav.pop()} rightIcon={I.heart} />
|
||||
<div style={{ padding: '0 20px 16px' }}>
|
||||
<div style={{ display: 'flex', gap: 16, alignItems: 'flex-start' }}>
|
||||
<Avatar init={doc.init} size={84} style={{ fontSize: 32 }} />
|
||||
<div style={{ flex: 1, paddingTop: 6 }}>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, lineHeight: 1.2, marginBottom: 4 }}>{doc.name}</div>
|
||||
<div style={{ fontSize: 14, color: 'var(--c-fg-3)', marginBottom: 8 }}>{doc.spec}</div>
|
||||
{doc.kmn && <span className="chip chip-warm">Кандидат мед. наук</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '0 20px 16px', display: 'grid', gridTemplateColumns: 'repeat(3,1fr)', gap: 10 }}>
|
||||
<div className="card" style={{ textAlign: 'center', padding: 12 }}>
|
||||
<div className="stat-big">{doc.exp}</div>
|
||||
<div className="sub" style={{ fontSize: 11 }}>лет опыта</div>
|
||||
</div>
|
||||
<div className="card" style={{ textAlign: 'center', padding: 12 }}>
|
||||
<div className="stat-big" style={{ display: 'flex', justifyContent: 'center', alignItems: 'baseline', gap: 3 }}>
|
||||
{doc.rating}<I.star size={16} style={{ color: '#E8A13C' }} />
|
||||
</div>
|
||||
<div className="sub" style={{ fontSize: 11 }}>{doc.reviews} отзыва</div>
|
||||
</div>
|
||||
<div className="card" style={{ textAlign: 'center', padding: 12 }}>
|
||||
<div className="stat-big">{doc.price}</div>
|
||||
<div className="sub" style={{ fontSize: 11 }}>₽ приём</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '0 20px 12px' }}>
|
||||
<div className="seg" style={{ width: '100%', display: 'flex' }}>
|
||||
{[['info','О враче'],['schedule','Расписание'],['reviews','Отзывы']].map(([id,lb]) => (
|
||||
<button key={id} onClick={() => setTab(id)} className={tab===id?'on':''} style={{ flex: 1 }}>{lb}</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
{tab === 'info' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
|
||||
<div className="card">
|
||||
<div className="h-row" style={{ marginBottom: 6 }}>Образование</div>
|
||||
<p style={{ margin: 0, fontSize: 14, color: 'var(--c-fg-2)', lineHeight: 1.55 }}>Пермский государственный медицинский университет им. акад. Е.А. Вагнера, специальность «Оториноларингология».</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="h-row" style={{ marginBottom: 6 }}>Специализация</div>
|
||||
<p style={{ margin: 0, fontSize: 14, color: 'var(--c-fg-2)', lineHeight: 1.55 }}>Эндоскопическая хирургия околоносовых пазух, лечение хронических отитов, слухопротезирование.</p>
|
||||
</div>
|
||||
<div className="card">
|
||||
<div className="h-row" style={{ marginBottom: 10 }}>Услуги и цены</div>
|
||||
{[
|
||||
['Первичный приём', doc.price],
|
||||
['Повторный приём', doc.price - 400],
|
||||
['Эндоскопия ЛОР-органов', 1200],
|
||||
].map(([n,p],i,a) => (
|
||||
<div key={n}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '10px 0' }}>
|
||||
<span style={{ fontSize: 14 }}>{n}</span>
|
||||
<span className="price" style={{ fontSize: 15 }}>{p} ₽</span>
|
||||
</div>
|
||||
{i < a.length - 1 && <div className="divider" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{tab === 'schedule' && (
|
||||
<div className="card">
|
||||
{['Пн 21 апр','Вт 22 апр','Ср 23 апр','Чт 24 апр'].map((d,i,a) => (
|
||||
<div key={d}>
|
||||
<div style={{ padding: '12px 0' }}>
|
||||
<div style={{ fontSize: 13, fontWeight: 700, marginBottom: 8 }}>{d}</div>
|
||||
<div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
|
||||
{['09:00','10:30','14:00','16:00','18:30'].slice(0, 5-i).map(t => (
|
||||
<span key={t} style={{ padding: '5px 10px', borderRadius: 8, background: 'var(--c-primary-100)', color: 'var(--c-primary-darker)', fontSize: 13, fontWeight: 700 }}>{t}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{i < a.length - 1 && <div className="divider" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{tab === 'reviews' && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{[
|
||||
{ n: 'Елена К.', d: '12 апр', r: 5, t: 'Доктор очень внимательная, всё объяснила простыми словами. Дочке 6 лет, нашли общий язык быстро.' },
|
||||
{ n: 'Михаил П.', d: '5 апр', r: 5, t: 'Профессионал высокого уровня. Операция прошла без осложнений, восстановление быстрое.' },
|
||||
{ n: 'Ольга Н.', d: '28 мар', r: 4, t: 'Хороший специалист. Жаль, что приём ждать долго в регистратуре.' },
|
||||
].map((r,i)=>(
|
||||
<div key={i} className="card">
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: 14 }}>{r.n}</div>
|
||||
<div style={{ display: 'flex', gap: 1 }}>{[1,2,3,4,5].map(s => <I.star key={s} size={13} style={{ color: s<=r.r?'#E8A13C':'#E4EAF2' }}/>)}</div>
|
||||
</div>
|
||||
<div className="sub" style={{ fontSize: 11, marginBottom: 8 }}>{r.d}</div>
|
||||
<div style={{ fontSize: 14, color: 'var(--c-fg-2)', lineHeight: 1.5 }}>{r.t}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '14px 20px 34px', background: 'rgba(255,255,255,0.95)', backdropFilter: 'blur(8px)', borderTop: '1px solid var(--c-border)' }}>
|
||||
<button onClick={() => nav.push('booking-time:' + doc.id)} className="btn-p block">Записаться · от {doc.price} ₽</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
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={i} className="press card" style={{
|
||||
flexShrink: 0, width: 220, padding: 0, overflow: 'hidden', textAlign: 'left',
|
||||
}}>
|
||||
<div style={{
|
||||
height: 90, background: ['#F5EDDF','#E3F4F2','#FDF8E6','#FCF1F0'][i % 4],
|
||||
display: 'flex', alignItems: 'flex-end', padding: 12,
|
||||
}}>
|
||||
<span className="chip chip-warm" style={{ background: 'rgba(255,255,255,0.85)' }}>{a.tag}</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 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="Полезно прочитать" pad="0 0 0 0" />
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{articles.slice(0,3).map((a,i)=>(
|
||||
<button key={i} className="press card" style={{ display: 'flex', gap: 12, alignItems: 'center', padding: 14, textAlign: 'left' }}>
|
||||
<div style={{ width: 56, height: 56, borderRadius: 12, background: ['#F5EDDF','#E3F4F2','#FDF8E6'][i%3], flexShrink: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 24 }}>
|
||||
{['📖','👂','🤱'][i%3]}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,757 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { I } from '../icons.jsx';
|
||||
import { CLINIC_DATA } from '../data.js';
|
||||
import { Avatar, AppointmentCard, ScreenHeader } from '../components.jsx';
|
||||
|
||||
export function ApptsTabScreen({ nav }) {
|
||||
const { appointments, doctors, clinic } = CLINIC_DATA;
|
||||
const [tab, setTab] = useState('upcoming');
|
||||
const items = appointments.filter(a => a.status === tab);
|
||||
return (
|
||||
<div style={{ paddingBottom: 100 }}>
|
||||
<div style={{ padding: '12px 20px 12px' }}>
|
||||
<h1 className="h-screen" style={{ marginBottom: 14 }}>Мои приёмы</h1>
|
||||
<div className="seg" style={{ width: '100%', display: 'flex' }}>
|
||||
<button onClick={() => setTab('upcoming')} className={tab==='upcoming'?'on':''} style={{ flex: 1 }}>Предстоящие · {appointments.filter(a=>a.status==='upcoming').length}</button>
|
||||
<button onClick={() => setTab('past')} className={tab==='past'?'on':''} style={{ flex: 1 }}>Прошедшие</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '0 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{items.map(a => {
|
||||
const d = doctors.find(x => x.id === a.doctor);
|
||||
const ad = clinic.addresses.find(x => x.id === a.address);
|
||||
return <AppointmentCard key={a.id} appt={a} doctor={d} addr={ad} onClick={() => nav.push('appt:' + a.id)} />;
|
||||
})}
|
||||
{items.length === 0 && (
|
||||
<div style={{ textAlign: 'center', padding: '40px 20px' }}>
|
||||
<div style={{ fontSize: 48, opacity: .3, marginBottom: 8 }}>📋</div>
|
||||
<div className="sub">Нет приёмов в этой категории</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ padding: 16, marginTop: 12 }}>
|
||||
<button onClick={() => nav.push('booking-specs')} className="btn-p block">
|
||||
<I.plus size={18} /> Записаться на приём
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ApptDetailScreen({ nav, apptId }) {
|
||||
const a = CLINIC_DATA.appointments.find(x => x.id === apptId);
|
||||
const d = CLINIC_DATA.doctors.find(x => x.id === a.doctor);
|
||||
const ad = CLINIC_DATA.clinic.addresses.find(x => x.id === a.address);
|
||||
const isUp = a.status === 'upcoming';
|
||||
return (
|
||||
<div style={{ paddingBottom: 120 }}>
|
||||
<ScreenHeader title="Приём" onBack={() => nav.pop()} />
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
<div className="card" style={{ padding: 20, textAlign: 'center', marginBottom: 14, background: isUp ? 'linear-gradient(135deg,#E3F4F2,#F2FAF9)' : '#fff' }}>
|
||||
<div style={{ fontSize: 14, color: 'var(--c-primary-darker)', fontWeight: 700, marginBottom: 6 }}>{a.weekday}, {a.date}</div>
|
||||
<div style={{ fontSize: 42, fontFamily: 'var(--font-narrow)', fontWeight: 700, color: 'var(--c-fg-1)', lineHeight: 1, marginBottom: 8 }}>{a.time}</div>
|
||||
<div className="sub">{a.type}</div>
|
||||
</div>
|
||||
<div className="card" style={{ padding: 0, marginBottom: 14 }}>
|
||||
<button onClick={() => nav.push('doctor:' + d.id)} style={{ width: '100%', textAlign: 'left', padding: 14, display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||
<Avatar init={d.init} size={48} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: 15 }}>{d.name.split(' ').slice(0,2).join(' ')}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{d.spec}</div>
|
||||
</div>
|
||||
<I.chev size={16} style={{ color: 'var(--c-fg-4)' }} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="card" style={{ padding: 0 }}>
|
||||
<div style={{ padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<I.pin size={20} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 700 }}>{ad.full}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{a.room} · 2 этаж</div>
|
||||
</div>
|
||||
<button className="btn-s" style={{ padding: '8px 12px', fontSize: 12 }}>Карта</button>
|
||||
</div>
|
||||
<div className="divider" />
|
||||
<div style={{ padding: '14px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||
<I.phone size={20} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
<div style={{ flex: 1, fontSize: 14 }}>(342) 207-03-03</div>
|
||||
<I.chev size={16} style={{ color: 'var(--c-fg-4)' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isUp && (
|
||||
<div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<button className="btn-g" style={{ width: '100%', padding: 14 }}>
|
||||
<I.calendar size={18} /> Добавить в календарь
|
||||
</button>
|
||||
<button className="btn-g" style={{ width: '100%', padding: 14 }}>
|
||||
<I.bell size={18} /> Напомнить позже
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{a.hasReport && (
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<div className="h-sec" style={{ marginBottom: 10 }}>Заключение врача</div>
|
||||
<div className="card">
|
||||
<div style={{ fontSize: 14, lineHeight: 1.55, color: 'var(--c-fg-2)' }}>
|
||||
Диагноз: хронический риносинусит, обострение. Назначено: Аква Марис, Назонекс 14 дней. Контрольный осмотр через 2 недели.
|
||||
</div>
|
||||
<button className="btn-s" style={{ marginTop: 12, padding: '8px 14px', fontSize: 13 }}>
|
||||
<I.doc size={15} /> Открыть PDF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{isUp && (
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, padding: '14px 20px 34px', background: '#fff', borderTop: '1px solid var(--c-border)', display: 'flex', gap: 10 }}>
|
||||
<button className="btn-g" style={{ flex: 1, padding: 14, color: 'var(--c-danger)', borderColor: 'var(--c-accent-50)' }}>Отменить</button>
|
||||
<button className="btn-p" style={{ flex: 2 }}>Перенести</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ResultsScreen({ nav }) {
|
||||
const { results, doctors } = CLINIC_DATA;
|
||||
return (
|
||||
<div style={{ paddingBottom: 100 }}>
|
||||
<ScreenHeader title="Анализы и обследования" onBack={() => nav.pop()} />
|
||||
<div className="pills" style={{ marginBottom: 12 }}>
|
||||
{['Все','Готовы','В работе','Аудио','Эндоскопия'].map((p,i)=>(
|
||||
<button key={i} className={'pill' + (i===0?' on':'')}>{p}</button>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ padding: '0 16px', display: 'flex', flexDirection: 'column', gap: 10 }}>
|
||||
{results.map(r => {
|
||||
const d = doctors.find(x => x.id === r.doctor);
|
||||
const isReady = r.status === 'ready';
|
||||
const kindIcons = { audio: I.hearing, image: I.video, lab: I.doc };
|
||||
const Ic = kindIcons[r.kind];
|
||||
return (
|
||||
<button key={r.id} onClick={() => isReady && nav.push(r.kind === 'audio' ? 'result-audio' : 'result:' + r.id)} className="press card" style={{
|
||||
display: 'flex', gap: 12, alignItems: 'center', textAlign: 'left', opacity: isReady ? 1 : .7,
|
||||
}}>
|
||||
<div style={{ width: 44, height: 44, borderRadius: 12, background: isReady ? 'var(--c-primary-100)' : 'var(--c-warning-50)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Ic size={22} style={{ color: isReady ? 'var(--c-primary-darker)' : 'var(--c-warning)' }} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontWeight: 700, fontSize: 15, marginBottom: 2 }}>{r.name}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{r.date} · {d.name.split(' ')[0]}</div>
|
||||
</div>
|
||||
{isReady ? <I.chev size={16} style={{ color: 'var(--c-fg-4)' }} /> : <span className="chip" style={{ background: 'var(--c-warning-50)', color: 'var(--c-warning)' }}>В работе</span>}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ResultAudioScreen({ nav }) {
|
||||
const freqs = [250, 500, 1000, 2000, 4000, 8000];
|
||||
const leftDB = [10, 15, 20, 25, 35, 50];
|
||||
const rightDB = [5, 10, 15, 20, 30, 40];
|
||||
const w = 320, h = 220, pl = 30, pt = 10, pr = 10, pb = 30;
|
||||
const iw = w - pl - pr, ih = h - pt - pb;
|
||||
const xFor = i => pl + (i / (freqs.length - 1)) * iw;
|
||||
const yFor = db => pt + (db / 120) * ih;
|
||||
return (
|
||||
<div style={{ paddingBottom: 40 }}>
|
||||
<ScreenHeader title="Аудиограмма" subtitle="8 апр 2026" onBack={() => nav.pop()} rightIcon={I.doc} />
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
<div className="card" style={{ marginBottom: 14 }}>
|
||||
<div style={{ display: 'flex', gap: 16, marginBottom: 12 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ width: 10, height: 10, borderRadius: 999, background: 'var(--c-accent)' }} />
|
||||
<span style={{ fontSize: 12, fontWeight: 700 }}>Правое ухо</span>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
<div style={{ width: 10, height: 10, background: 'var(--c-primary-darker)' }} />
|
||||
<span style={{ fontSize: 12, fontWeight: 700 }}>Левое ухо</span>
|
||||
</div>
|
||||
</div>
|
||||
<svg width="100%" viewBox={`0 0 ${w} ${h}`} style={{ display: 'block' }}>
|
||||
{[0, 20, 40, 60, 80, 100, 120].map(db => (
|
||||
<g key={db}>
|
||||
<line x1={pl} y1={yFor(db)} x2={w - pr} y2={yFor(db)} stroke="#E4EAF2" strokeWidth="1" />
|
||||
<text x={pl - 6} y={yFor(db) + 4} fontSize="10" fill="#9AA7B4" textAnchor="end">{db}</text>
|
||||
</g>
|
||||
))}
|
||||
{freqs.map((f, i) => (
|
||||
<g key={f}>
|
||||
<line x1={xFor(i)} y1={pt} x2={xFor(i)} y2={h - pb} stroke="#E4EAF2" strokeWidth="1" />
|
||||
<text x={xFor(i)} y={h - pb + 14} fontSize="10" fill="#9AA7B4" textAnchor="middle">{f < 1000 ? f : (f/1000) + 'k'}</text>
|
||||
</g>
|
||||
))}
|
||||
<rect x={pl} y={pt} width={iw} height={yFor(25)-pt} fill="#E8F5EE" opacity=".5" />
|
||||
<polyline points={rightDB.map((db,i)=>`${xFor(i)},${yFor(db)}`).join(' ')} fill="none" stroke="#E04E44" strokeWidth="2" />
|
||||
{rightDB.map((db,i)=>(<circle key={i} cx={xFor(i)} cy={yFor(db)} r="4" fill="#fff" stroke="#E04E44" strokeWidth="2" />))}
|
||||
<polyline points={leftDB.map((db,i)=>`${xFor(i)},${yFor(db)}`).join(' ')} fill="none" stroke="#166B63" strokeWidth="2" strokeDasharray="4 3" />
|
||||
{leftDB.map((db,i)=>(
|
||||
<g key={i} stroke="#166B63" strokeWidth="2">
|
||||
<line x1={xFor(i)-4} y1={yFor(db)-4} x2={xFor(i)+4} y2={yFor(db)+4} />
|
||||
<line x1={xFor(i)+4} y1={yFor(db)-4} x2={xFor(i)-4} y2={yFor(db)+4} />
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
<div className="sub" style={{ fontSize: 11, textAlign: 'center', marginTop: 8 }}>dB HL / частота (Гц) · зелёная зона — норма</div>
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ marginBottom: 14 }}>
|
||||
<div className="h-row" style={{ marginBottom: 8 }}>Заключение</div>
|
||||
<div style={{ fontSize: 14, color: 'var(--c-fg-2)', lineHeight: 1.55, marginBottom: 12 }}>
|
||||
Правое ухо — норма. Левое ухо — лёгкая нейросенсорная тугоухость в области высоких частот (4–8 кГц).
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', padding: 12, background: 'var(--c-primary-50)', borderRadius: 10 }}>
|
||||
<I.stetho size={18} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
<div style={{ fontSize: 13, color: 'var(--c-fg-2)' }}>Рекомендован контроль через 6 месяцев</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="btn-g" style={{ width: '100%', padding: 14 }}>
|
||||
<I.doc size={18} /> Скачать заключение
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function RecoveryScreen({ nav }) {
|
||||
const { recovery } = CLINIC_DATA;
|
||||
const surgeon = CLINIC_DATA.doctors.find(d => d.id === recovery.surgeon);
|
||||
return (
|
||||
<div style={{ paddingBottom: 40 }}>
|
||||
<ScreenHeader title="Восстановление" onBack={() => nav.pop()} />
|
||||
<div style={{ padding: '0 20px 16px' }}>
|
||||
<div className="card" style={{ padding: 18, marginBottom: 14, background: 'linear-gradient(135deg, var(--c-primary-darker), #0F4A44)', color: '#fff', border: 0 }}>
|
||||
<div style={{ fontSize: 12, textTransform: 'uppercase', letterSpacing: .8, opacity: .7, marginBottom: 4 }}>Операция</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, marginBottom: 14 }}>{recovery.op}</div>
|
||||
<div style={{ height: 8, background: 'rgba(255,255,255,0.2)', borderRadius: 999, overflow: 'hidden', marginBottom: 12 }}>
|
||||
<div style={{ width: `${recovery.dayNow/recovery.totalDays*100}%`, height: '100%', background: '#fff', borderRadius: 999 }} />
|
||||
</div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12 }}>
|
||||
<span style={{ opacity: .8 }}>День {recovery.dayNow}</span>
|
||||
<span style={{ opacity: .8 }}>{recovery.totalDays - recovery.dayNow} дней до выписки</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-sec" style={{ marginBottom: 10 }}>Лекарства сегодня</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginBottom: 18 }}>
|
||||
{recovery.meds.map((m,i)=>(
|
||||
<div key={i} className="card" style={{ display: 'flex', alignItems: 'center', gap: 12, padding: 14 }}>
|
||||
<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 }}>{m.name}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{m.freq} · след. {m.nextTake}</div>
|
||||
</div>
|
||||
{m.total > 0 && (
|
||||
<div style={{ fontSize: 12, color: 'var(--c-fg-3)', fontWeight: 700 }}>{m.taken}/{m.total}</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="h-sec" style={{ marginBottom: 10 }}>План восстановления</div>
|
||||
<div className="card" style={{ padding: 0 }}>
|
||||
{recovery.steps.map((s, i) => (
|
||||
<div key={i} style={{ display: 'flex', gap: 14, padding: '14px 16px', alignItems: 'flex-start', position: 'relative' }}>
|
||||
{i < recovery.steps.length - 1 && (
|
||||
<div style={{ position: 'absolute', left: 27, top: 36, bottom: -10, width: 2, background: s.done ? 'var(--c-primary-darker)' : 'var(--c-divider)' }} />
|
||||
)}
|
||||
<div style={{
|
||||
width: 28, height: 28, borderRadius: 999, flexShrink: 0,
|
||||
background: s.done ? 'var(--c-primary-darker)' : '#fff',
|
||||
border: s.active ? '3px solid var(--c-primary-darker)' : s.done ? 0 : '2px solid var(--c-border-strong)',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
position: 'relative', zIndex: 1,
|
||||
}}>
|
||||
{s.done && <I.check size={16} style={{ color: '#fff' }} sw={3} />}
|
||||
{s.active && <div style={{ width: 10, height: 10, borderRadius: 999, background: 'var(--c-primary-darker)' }} />}
|
||||
</div>
|
||||
<div style={{ flex: 1, paddingBottom: 4 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, color: s.done ? 'var(--c-fg-3)' : 'var(--c-fg-1)', marginBottom: 2 }}>{s.title}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{s.note}</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ marginTop: 14, display: 'flex', gap: 12, alignItems: 'center', padding: 14 }}>
|
||||
<Avatar init={surgeon.init} size={44} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div className="sub" style={{ fontSize: 11 }}>Ваш хирург</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700 }}>{surgeon.name.split(' ').slice(0,2).join(' ')}</div>
|
||||
</div>
|
||||
<button onClick={() => nav.push('chat')} className="btn-s" style={{ padding: '8px 12px' }}>
|
||||
<I.chat size={15} /> Чат
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function AudioTestScreen({ nav }) {
|
||||
const [stage, setStage] = useState('intro');
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [currFreq, setCurrFreq] = useState(500);
|
||||
|
||||
useEffect(() => {
|
||||
if (stage !== 'test') return;
|
||||
const id = setInterval(() => {
|
||||
setProgress(p => {
|
||||
if (p >= 100) { clearInterval(id); setStage('done'); return 100; }
|
||||
return p + 2;
|
||||
});
|
||||
setCurrFreq([250,500,1000,2000,4000,8000][Math.floor(Math.random()*6)]);
|
||||
}, 200);
|
||||
return () => clearInterval(id);
|
||||
}, [stage]);
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 40, height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<ScreenHeader title="Тест слуха" onBack={() => nav.pop()} />
|
||||
<div style={{ flex: 1, padding: '0 20px', display: 'flex', flexDirection: 'column' }}>
|
||||
{stage === 'intro' && (
|
||||
<>
|
||||
<div style={{ textAlign: 'center', padding: '24px 0' }}>
|
||||
<div style={{ width: 120, height: 120, margin: '0 auto 20px', borderRadius: 999, background: 'var(--c-primary-100)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.hearing size={60} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
</div>
|
||||
<h1 className="h-screen" style={{ marginBottom: 10 }}>Проверим Ваш слух</h1>
|
||||
<p className="sub" style={{ fontSize: 14, maxWidth: 300, margin: '0 auto' }}>Тест займёт 3 минуты. Результат не заменяет консультацию сурдолога, но покажет, стоит ли записаться на приём.</p>
|
||||
</div>
|
||||
<div className="card" style={{ marginBottom: 14 }}>
|
||||
<div className="h-row" style={{ marginBottom: 10 }}>Для точного теста</div>
|
||||
{[
|
||||
{ i: I.volume, t: 'Наденьте наушники', s: 'Без Bluetooth, проводные лучше' },
|
||||
{ i: I.shield, t: 'Выберите тихое место', s: 'Без фоновых звуков и разговоров' },
|
||||
{ i: I.clock, t: 'Не торопитесь', s: 'Отвечайте, когда уверены' },
|
||||
].map((x,i,a)=>{
|
||||
const XI = x.i;
|
||||
return (
|
||||
<div key={i}>
|
||||
<div style={{ display: 'flex', gap: 12, padding: '10px 0', alignItems: 'flex-start' }}>
|
||||
<XI size={20} style={{ color: 'var(--c-primary-darker)', flexShrink: 0, marginTop: 1 }} />
|
||||
<div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 2 }}>{x.t}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{x.s}</div>
|
||||
</div>
|
||||
</div>
|
||||
{i < a.length - 1 && <div className="divider" />}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div style={{ flex: 1 }} />
|
||||
<button onClick={() => setStage('test')} className="btn-p block" style={{ marginBottom: 10 }}>Начать тест</button>
|
||||
<div className="sub" style={{ fontSize: 12, textAlign: 'center' }}>Регулярные тесты помогают заметить изменения слуха вовремя</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{stage === 'test' && (
|
||||
<>
|
||||
<div style={{ padding: '16px 0 8px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 6 }}>
|
||||
<span className="sub" style={{ fontSize: 12 }}>Правое ухо · {currFreq} Гц</span>
|
||||
<span className="sub" style={{ fontSize: 12 }}>{Math.round(progress)}%</span>
|
||||
</div>
|
||||
<div style={{ height: 6, background: 'var(--c-divider)', borderRadius: 999, overflow: 'hidden' }}>
|
||||
<div style={{ width: `${progress}%`, height: '100%', background: 'var(--c-primary-darker)', borderRadius: 999, transition: 'width .2s' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ position: 'relative', width: 220, height: 220, marginBottom: 24 }}>
|
||||
{[0,1,2].map(i => (
|
||||
<div key={i} style={{
|
||||
position: 'absolute', inset: -20 * i, borderRadius: 999,
|
||||
border: '2px solid var(--c-primary-200)',
|
||||
opacity: 0.5 - i * 0.15,
|
||||
animation: `pulse 1.4s ${i * 0.3}s ease-out infinite`,
|
||||
}} />
|
||||
))}
|
||||
<div style={{ position: 'absolute', inset: 0, borderRadius: 999, background: 'var(--c-primary-darker)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.volume size={70} style={{ color: '#fff' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700, textAlign: 'center', marginBottom: 8 }}>Слышите звук?</div>
|
||||
<div className="sub" style={{ fontSize: 14, textAlign: 'center', maxWidth: 260 }}>Нажмите кнопку сразу, как услышите тон — даже если очень тихо</div>
|
||||
</div>
|
||||
<button className="btn-p block" style={{ padding: 22, fontSize: 17, marginBottom: 8 }}>Слышу!</button>
|
||||
<button className="btn-g" style={{ width: '100%', padding: 14 }}>Не слышу</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{stage === 'done' && (
|
||||
<>
|
||||
<div style={{ textAlign: 'center', padding: '24px 0' }}>
|
||||
<div style={{ width: 96, height: 96, margin: '0 auto 20px', borderRadius: 999, background: 'var(--c-success-50)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.check size={48} style={{ color: 'var(--c-success)' }} sw={3} />
|
||||
</div>
|
||||
<h1 className="h-screen" style={{ marginBottom: 8 }}>Тест завершён</h1>
|
||||
<p className="sub" style={{ fontSize: 14 }}>Ваш слух в пределах нормы</p>
|
||||
</div>
|
||||
<div className="card" style={{ marginBottom: 14 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
||||
<div>
|
||||
<div className="sub" style={{ fontSize: 11, marginBottom: 4 }}>Правое ухо</div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700, color: 'var(--c-success)' }}>Норма</div>
|
||||
<div className="sub" style={{ fontSize: 12, marginTop: 2 }}>10 дБ, все частоты</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="sub" style={{ fontSize: 11, marginBottom: 4 }}>Левое ухо</div>
|
||||
<div style={{ fontSize: 18, fontWeight: 700, color: 'var(--c-warning)' }}>Почти норма</div>
|
||||
<div className="sub" style={{ fontSize: 12, marginTop: 2 }}>25 дБ на 4 кГц</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card" style={{ marginBottom: 14, background: 'var(--c-primary-50)' }}>
|
||||
<div style={{ display: 'flex', gap: 10, alignItems: 'flex-start' }}>
|
||||
<I.stetho size={20} style={{ color: 'var(--c-primary-darker)', flexShrink: 0, marginTop: 2 }} />
|
||||
<div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 4 }}>Рекомендация</div>
|
||||
<div style={{ fontSize: 13, color: 'var(--c-fg-2)', lineHeight: 1.5 }}>Небольшое снижение на левом ухе. Рекомендуем пройти аудиометрию у сурдолога.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 1 }} />
|
||||
<button onClick={() => nav.push('booking-specs')} className="btn-p block" style={{ marginBottom: 10 }}>Записаться к сурдологу</button>
|
||||
<button onClick={() => nav.pop()} className="btn-g" style={{ width: '100%', padding: 14 }}>Сохранить и закрыть</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatTabScreen() {
|
||||
const msgs = [
|
||||
{ from: 'doc', t: 'Добрый день, Анна! Как самочувствие после операции?', tm: '14:02' },
|
||||
{ from: 'me', t: 'Здравствуйте! В целом хорошо, немного саднит в носу по утрам.', tm: '14:08' },
|
||||
{ from: 'doc', t: 'Это нормально на 6-й день. Продолжайте промывания Аква Марис 4 раза в день.', tm: '14:10' },
|
||||
{ from: 'doc', t: 'Выходите на осмотр сегодня? Я свободен после 15:00.', tm: '14:11' },
|
||||
{ from: 'me', t: 'Да, буду в 16:00 как запланировано.', tm: '14:14' },
|
||||
{ from: 'doc', t: 'Отлично, жду. Если что-то изменится — напишите.', tm: '14:15' },
|
||||
];
|
||||
const doc = CLINIC_DATA.doctors.find(d => d.id === 'syndaev');
|
||||
return (
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ padding: '12px 20px 12px' }}>
|
||||
<h1 className="h-screen">Чат</h1>
|
||||
</div>
|
||||
<div style={{ padding: '0 16px 12px' }}>
|
||||
<div className="card" style={{ display: 'flex', gap: 12, alignItems: 'center', padding: 12, background: 'var(--c-primary-50)' }}>
|
||||
<Avatar init={doc.init} size={44} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 700 }}>{doc.name.split(' ').slice(0,2).join(' ')}</div>
|
||||
<div className="sub" style={{ fontSize: 12, display: 'flex', alignItems: 'center', gap: 4 }}>
|
||||
<span style={{ width: 6, height: 6, borderRadius: 999, background: 'var(--c-success)' }} />
|
||||
Онлайн · отвечает 5 мин
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn-s" style={{ padding: 10, borderRadius: 999 }}>
|
||||
<I.video size={16} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 1, overflowY: 'auto', padding: '8px 16px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{msgs.map((m,i)=>(
|
||||
<div key={i} style={{ alignSelf: m.from === 'me' ? 'flex-end' : 'flex-start', maxWidth: '78%' }}>
|
||||
<div style={{
|
||||
background: m.from === 'me' ? 'var(--c-primary-darker)' : '#fff',
|
||||
color: m.from === 'me' ? '#fff' : 'var(--c-fg-1)',
|
||||
padding: '10px 14px', borderRadius: 16,
|
||||
borderBottomRightRadius: m.from === 'me' ? 4 : 16,
|
||||
borderBottomLeftRadius: m.from === 'me' ? 16 : 4,
|
||||
fontSize: 14, lineHeight: 1.45,
|
||||
boxShadow: m.from === 'me' ? 'none' : 'var(--sh-sm)',
|
||||
}}>{m.t}</div>
|
||||
<div className="sub" style={{ fontSize: 11, marginTop: 3, textAlign: m.from === 'me' ? 'right' : 'left', padding: '0 4px' }}>{m.tm}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div style={{ padding: '10px 16px 100px', borderTop: '1px solid var(--c-border)', background: '#fff' }}>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<button style={{ width: 38, height: 38, borderRadius: 999, background: 'var(--c-bg)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.plus size={20} />
|
||||
</button>
|
||||
<div style={{ flex: 1, background: 'var(--c-bg)', borderRadius: 999, padding: '10px 16px', fontSize: 14, color: 'var(--c-fg-4)' }}>Сообщение...</div>
|
||||
<button style={{ width: 38, height: 38, borderRadius: 999, background: 'var(--c-primary-darker)', color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.mic size={20} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ProfileTabScreen({ nav }) {
|
||||
const sections = [
|
||||
{
|
||||
title: 'Здоровье',
|
||||
items: [
|
||||
{ i: I.file, t: 'Медицинская карта', s: 'История, диагнозы', go: 'medcard' },
|
||||
{ i: I.doc, t: 'Анализы', s: '5 результатов', go: 'results' },
|
||||
{ i: I.pill, t: 'Лекарства', s: '3 активных курса', go: 'recovery' },
|
||||
{ i: I.hearing, t: 'История тестов слуха', s: '2 аудиограммы', go: 'results' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Оплата и бонусы',
|
||||
items: [
|
||||
{ i: I.card, t: 'Способы оплаты', s: 'Mir •••• 4821' },
|
||||
{ i: I.gift, t: 'Бонусы', s: '2 480 баллов · 5%', badge: 'Серебро' },
|
||||
{ i: I.file, t: 'История платежей', s: '12 операций' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Клиника',
|
||||
items: [
|
||||
{ i: I.pin, t: 'Адреса и часы работы', s: '3 клиники' },
|
||||
{ i: I.phone, t: '(342) 207-03-03', s: 'Ежедневно 9:00–21:00' },
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Настройки',
|
||||
items: [
|
||||
{ i: I.bell, t: 'Уведомления' },
|
||||
{ i: I.shield, t: 'Конфиденциальность' },
|
||||
{ i: I.user, t: 'Члены семьи', s: '+ 2 профиля' },
|
||||
]
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div style={{ paddingBottom: 100 }}>
|
||||
<div style={{ padding: '12px 20px 16px' }}>
|
||||
<h1 className="h-screen" style={{ marginBottom: 18 }}>Профиль</h1>
|
||||
<div className="card" style={{ padding: 18, display: 'flex', gap: 14, alignItems: 'center', background: 'linear-gradient(135deg, var(--c-primary-100), var(--c-warm-100))', border: 0 }}>
|
||||
<Avatar init="АС" size={64} style={{ fontSize: 24, boxShadow: 'var(--sh-sm)' }} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 18, fontWeight: 700 }}>Анна Сергеевна</div>
|
||||
<div className="sub" style={{ fontSize: 13, marginBottom: 6 }}>+7 (912) 485-••-•• · 42 года</div>
|
||||
<button onClick={() => nav.push('qr')} className="chip" style={{ fontSize: 12, fontWeight: 700 }}>
|
||||
<I.qr size={12} /> QR пациента
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '0 16px' }}>
|
||||
{sections.map((sec, si) => (
|
||||
<div key={si} style={{ marginBottom: 18 }}>
|
||||
<div style={{ fontSize: 11, color: 'var(--c-fg-3)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: .8, padding: '0 4px 8px' }}>{sec.title}</div>
|
||||
<div className="card" style={{ padding: 0 }}>
|
||||
{sec.items.map((it, i) => {
|
||||
const II = it.i;
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
<button onClick={() => it.go && nav.push(it.go)} className="press" style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 14, padding: '13px 16px', textAlign: 'left' }}>
|
||||
<div style={{ width: 34, height: 34, borderRadius: 9, background: 'var(--c-primary-100)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<II size={18} style={{ color: 'var(--c-primary-darker)' }} />
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 14, fontWeight: 600 }}>{it.t}</div>
|
||||
{it.s && <div className="sub" style={{ fontSize: 12 }}>{it.s}</div>}
|
||||
</div>
|
||||
{it.badge && <span className="chip chip-warm" style={{ fontSize: 11 }}>{it.badge}</span>}
|
||||
<I.chev size={15} style={{ color: 'var(--c-fg-4)' }} />
|
||||
</button>
|
||||
{i < sec.items.length - 1 && <div style={{ height: 1, background: 'var(--c-divider)', marginLeft: 64 }} />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function QRScreen({ nav }) {
|
||||
const cells = [];
|
||||
for (let i = 0; i < 441; i++) cells.push(Math.random() > 0.52 ? 1 : 0);
|
||||
const marker = (cx, cy) => {
|
||||
for (let y = 0; y < 7; y++) {
|
||||
for (let x = 0; x < 7; x++) {
|
||||
const isEdge = x === 0 || x === 6 || y === 0 || y === 6;
|
||||
const isInner = x >= 2 && x <= 4 && y >= 2 && y <= 4;
|
||||
cells[(cy + y) * 21 + (cx + x)] = (isEdge || isInner) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
marker(0, 0); marker(14, 0); marker(0, 14);
|
||||
|
||||
return (
|
||||
<div style={{ paddingBottom: 40, height: '100%', display: 'flex', flexDirection: 'column', background: 'linear-gradient(180deg, var(--c-primary-darker) 0%, var(--c-primary-dark) 100%)', color: '#fff' }}>
|
||||
<div style={{ display: 'flex', padding: '12px 16px 8px' }}>
|
||||
<button onClick={() => nav.pop()} className="press" style={{ width: 38, height: 38, borderRadius: 999, background: 'rgba(255,255,255,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.chevL size={20} style={{ color: '#fff' }} />
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '0 32px' }}>
|
||||
<div style={{ marginBottom: 24, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 22, fontWeight: 700, marginBottom: 6 }}>Ваш QR-код</div>
|
||||
<div style={{ fontSize: 13, opacity: .75 }}>Покажите на ресепшен, чтобы быстро отметиться</div>
|
||||
</div>
|
||||
<div style={{ background: '#fff', borderRadius: 24, padding: 20, marginBottom: 20 }}>
|
||||
<div style={{ width: 210, height: 210, display: 'grid', gridTemplateColumns: 'repeat(21,1fr)', gap: 0 }}>
|
||||
{cells.map((c, i) => (
|
||||
<div key={i} style={{ background: c ? '#0F4A44' : 'transparent' }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 13, opacity: .75, marginBottom: 3 }}>Пациент №</div>
|
||||
<div style={{ fontSize: 20, fontWeight: 700, letterSpacing: 1, fontFamily: 'var(--font-narrow)' }}>УГН-2014-00482</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '0 20px 20px' }}>
|
||||
<div style={{ background: 'rgba(255,255,255,0.12)', borderRadius: 16, padding: 14, display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||
<I.shield size={22} style={{ color: '#fff', opacity: .8 }} />
|
||||
<div style={{ fontSize: 13, lineHeight: 1.45, opacity: .9 }}>Код обновляется каждые 60 секунд. Не передавайте третьим лицам.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TelemedScreen({ nav }) {
|
||||
return (
|
||||
<div style={{ height: '100%', display: 'flex', flexDirection: 'column', background: '#0F1A20', color: '#fff', position: 'relative' }}>
|
||||
<div style={{ flex: 1, background: 'linear-gradient(135deg, #1F8F85, #166B63)', position: 'relative' }}>
|
||||
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ width: 140, height: 140, borderRadius: 999, background: 'rgba(255,255,255,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: 'var(--font-narrow)', fontSize: 56, fontWeight: 700 }}>СА</div>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', top: 60, left: 0, right: 0, padding: '0 20px', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<button onClick={() => nav.pop()} className="press" style={{ width: 36, height: 36, borderRadius: 999, background: 'rgba(0,0,0,0.4)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.chevD size={22} style={{ color: '#fff' }} />
|
||||
</button>
|
||||
<div style={{ background: 'rgba(0,0,0,0.4)', borderRadius: 999, padding: '8px 14px', display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<span style={{ width: 8, height: 8, borderRadius: 999, background: '#E04E44', animation: 'blink 1.2s infinite' }} />
|
||||
<span style={{ fontSize: 13, fontWeight: 700 }}>05:42</span>
|
||||
</div>
|
||||
<button className="press" style={{ width: 36, height: 36, borderRadius: 999, background: 'rgba(0,0,0,0.4)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<I.menu size={20} style={{ color: '#fff' }} />
|
||||
</button>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', bottom: 160, left: 0, right: 0, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 18, fontWeight: 700, marginBottom: 3 }}>Синдяев А.В.</div>
|
||||
<div style={{ fontSize: 13, opacity: .75 }}>ЛОР-хирург</div>
|
||||
</div>
|
||||
<div style={{ position: 'absolute', top: 110, right: 16, width: 96, height: 130, borderRadius: 14, background: '#2a3540', border: '2px solid rgba(255,255,255,0.2)', overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<div style={{ width: 44, height: 44, borderRadius: 999, background: 'var(--c-primary-100)', color: 'var(--c-primary-darker)', fontWeight: 700, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>АС</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ padding: '24px 0 50px', background: 'rgba(15,26,32,0.9)', backdropFilter: 'blur(10px)', display: 'flex', justifyContent: 'center', gap: 14 }}>
|
||||
{[
|
||||
{ i: I.mic, bg: 'rgba(255,255,255,0.15)' },
|
||||
{ i: I.video, bg: 'rgba(255,255,255,0.15)' },
|
||||
{ i: I.chat, bg: 'rgba(255,255,255,0.15)' },
|
||||
{ i: I.phone, bg: '#E04E44' },
|
||||
].map((c,i)=>{
|
||||
const CI = c.i;
|
||||
return (
|
||||
<button key={i} className="press" style={{ width: 58, height: 58, borderRadius: 999, background: c.bg, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
<CI size={24} style={{ color: '#fff' }} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MedcardScreen({ nav }) {
|
||||
return (
|
||||
<div style={{ paddingBottom: 40 }}>
|
||||
<ScreenHeader title="Медицинская карта" onBack={() => nav.pop()} />
|
||||
<div style={{ padding: '0 20px' }}>
|
||||
<div className="card" style={{ marginBottom: 14 }}>
|
||||
<div className="h-row" style={{ marginBottom: 10 }}>Основное</div>
|
||||
{[['Пол','Женский'],['Возраст','42 года'],['Рост / Вес','168 см · 62 кг'],['Группа крови','II (A), Rh+']].map(([k,v])=>(
|
||||
<div key={k} style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
|
||||
<span className="sub">{k}</span>
|
||||
<span style={{ fontSize: 14, fontWeight: 700 }}>{v}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="card" style={{ marginBottom: 14 }}>
|
||||
<div className="h-row" style={{ marginBottom: 10 }}>Аллергии</div>
|
||||
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
|
||||
<span className="chip chip-danger">Пенициллин</span>
|
||||
<span className="chip chip-danger">Пыльца берёзы</span>
|
||||
<span className="chip chip-soft">+ добавить</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-sec" style={{ padding: '4px 4px 10px' }}>История диагнозов</div>
|
||||
<div className="card" style={{ padding: 0 }}>
|
||||
{[
|
||||
{ d: '12 апр 2026', t: 'Искривление носовой перегородки', doc: 'Синдяев А.В.', tag: 'Операция' },
|
||||
{ d: '8 апр 2026', t: 'Хронический риносинусит', doc: 'Макарова Л.Г.', tag: 'Приём' },
|
||||
{ d: '15 ноя 2025', t: 'ОРВИ', doc: 'Суворова С.В.', tag: 'Приём' },
|
||||
].map((r,i,a)=>(
|
||||
<div key={i}>
|
||||
<div style={{ padding: '14px 16px' }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
|
||||
<span className="sub" style={{ fontSize: 12 }}>{r.d}</span>
|
||||
<span className="chip chip-soft" style={{ fontSize: 10 }}>{r.tag}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 700, marginBottom: 3 }}>{r.t}</div>
|
||||
<div className="sub" style={{ fontSize: 12 }}>{r.doc}</div>
|
||||
</div>
|
||||
{i < a.length - 1 && <div className="divider" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function NotificationsScreen({ nav }) {
|
||||
const n = [
|
||||
{ tm: '2 ч назад', t: 'Напоминание', s: 'Приём Амоксиклава в 20:00', i: I.pill, tint: 'warning' },
|
||||
{ tm: 'Сегодня', t: 'Готово заключение', s: 'Макарова Л.Г. — эндоскопия носоглотки', i: I.doc, tint: 'primary' },
|
||||
{ tm: 'Вчера', t: 'Новое сообщение', s: 'Синдяев А.В.: «Как самочувствие?»', i: I.chat, tint: 'primary' },
|
||||
{ tm: '15 апр', t: 'Акция', s: 'Бесплатная консультация хирурга в апреле', i: I.gift, tint: 'warm' },
|
||||
{ tm: '12 апр', t: 'Приём завершён', s: 'Не забудьте оставить отзыв о враче', i: I.star, tint: 'warning' },
|
||||
];
|
||||
const tints = {
|
||||
primary: { bg: 'var(--c-primary-100)', c: 'var(--c-primary-darker)' },
|
||||
warning: { bg: 'var(--c-warning-50)', c: 'var(--c-warning)' },
|
||||
warm: { bg: 'var(--c-warm-100)', c: 'var(--c-warm-text)' },
|
||||
};
|
||||
return (
|
||||
<div style={{ paddingBottom: 40 }}>
|
||||
<ScreenHeader title="Уведомления" onBack={() => nav.pop()} />
|
||||
<div style={{ padding: '0 16px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
{n.map((x,i)=>{
|
||||
const t = tints[x.tint];
|
||||
const XI = x.i;
|
||||
return (
|
||||
<div key={i} className="card" style={{ display: 'flex', gap: 12, alignItems: 'flex-start', padding: 14 }}>
|
||||
<div style={{ width: 38, height: 38, borderRadius: 10, background: t.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<XI size={18} style={{ color: t.c }} />
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 2 }}>
|
||||
<span style={{ fontSize: 14, fontWeight: 700 }}>{x.t}</span>
|
||||
<span className="sub" style={{ fontSize: 11 }}>{x.tm}</span>
|
||||
</div>
|
||||
<div className="sub" style={{ fontSize: 13 }}>{x.s}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user