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:
2026-04-19 18:16:59 +05:00
commit 95c9889b5d
18 changed files with 4542 additions and 0 deletions
+82
View File
@@ -0,0 +1,82 @@
import React, { useEffect, useMemo, useState } from 'react';
import { TabBar } from './components.jsx';
import { HomeCardsScreen, HomeListScreen, HomeFeedScreen } from './screens/screens-home.jsx';
import {
BookingSpecsScreen, BookingDoctorScreen, BookingTimeScreen,
BookingConfirmScreen, BookingSuccessScreen,
DoctorsTabScreen, DoctorDetailScreen,
} from './screens/screens-booking.jsx';
import {
ApptsTabScreen, ApptDetailScreen,
ResultsScreen, ResultAudioScreen,
RecoveryScreen, AudioTestScreen,
ChatTabScreen, ProfileTabScreen, QRScreen,
TelemedScreen, MedcardScreen, NotificationsScreen,
} from './screens/screens-misc.jsx';
function renderScreen(screenId, nav, ctx) {
const parts = screenId.split(':');
const id = parts[0];
const HOME = { cards: HomeCardsScreen, list: HomeListScreen, feed: HomeFeedScreen }[ctx.homeVariant] || HomeCardsScreen;
switch (id) {
case 'home': return <HOME nav={nav} ctx={ctx} />;
case 'doctors': return <DoctorsTabScreen nav={nav} ctx={ctx} />;
case 'doctor': return <DoctorDetailScreen nav={nav} doctorId={parts[1]} />;
case 'booking-specs': return <BookingSpecsScreen nav={nav} />;
case 'booking-doctor': return <BookingDoctorScreen nav={nav} specId={parts[1]} />;
case 'booking-time': return <BookingTimeScreen nav={nav} doctorId={parts[1]} />;
case 'booking-confirm': return <BookingConfirmScreen nav={nav} doctorId={parts[1]} dateIdx={+parts[2]} time={parts[3]} />;
case 'booking-success': return <BookingSuccessScreen nav={nav} />;
case 'appts': return <ApptsTabScreen nav={nav} />;
case 'appt': return <ApptDetailScreen nav={nav} apptId={parts[1]} />;
case 'results': return <ResultsScreen nav={nav} />;
case 'result-audio': return <ResultAudioScreen nav={nav} />;
case 'recovery': return <RecoveryScreen nav={nav} />;
case 'audiotest': return <AudioTestScreen nav={nav} />;
case 'chat': return <ChatTabScreen nav={nav} />;
case 'profile': return <ProfileTabScreen nav={nav} ctx={ctx} />;
case 'qr': return <QRScreen nav={nav} />;
case 'telemed': return <TelemedScreen nav={nav} />;
case 'medcard': return <MedcardScreen nav={nav} />;
case 'notifications': return <NotificationsScreen nav={nav} />;
default: return <div style={{padding: 40, textAlign: 'center'}}>Экран не найден: {screenId}</div>;
}
}
const TAB_IDS = ['home', 'appts', 'doctors', 'chat', 'profile'];
export function PhoneApp({ initialScreen, ctx }) {
const [stack, setStack] = useState([initialScreen]);
useEffect(() => { setStack([initialScreen]); }, [initialScreen]);
const nav = useMemo(() => ({
push: (id) => setStack(s => [...s, id]),
pop: () => setStack(s => s.length > 1 ? s.slice(0, -1) : s),
set: (id) => setStack([id]),
reset:() => setStack(['home']),
}), []);
const current = stack[stack.length - 1];
const tabId = TAB_IDS.includes(current.split(':')[0]) ? current.split(':')[0] : null;
const showTabBar = tabId !== null;
const modalScreens = ['qr', 'telemed', 'booking-success', 'audiotest'];
const isModal = modalScreens.includes(current.split(':')[0]);
return (
<div style={{
position: 'absolute', inset: 0,
background: 'var(--c-bg)',
overflow: 'hidden',
fontFamily: 'var(--font-base)',
}}>
<div style={{ position: 'absolute', inset: 0, overflowY: 'auto', overflowX: 'hidden', paddingTop: 58, paddingBottom: showTabBar ? 80 : 0 }}>
{renderScreen(current, nav, ctx)}
</div>
{showTabBar && !isModal && (
<TabBar active={tabId} onChange={(t) => nav.set(t)} />
)}
</div>
);
}