From 694b053a10a98ae55c80f50598aebe423b52baaa Mon Sep 17 00:00:00 2001 From: Tatyana Date: Thu, 31 Jul 2025 16:25:43 +0300 Subject: [PATCH] welcome --- src/App.tsx | 8 +- src/AppRoutes.tsx | 6 +- src/components/ExerciseItem.tsx | 154 ++++++++++++++++++++ src/components/Header.tsx | 173 ++++------------------- src/index.css | 14 ++ src/pages/Account.tsx | 69 +++++++++ src/pages/Exercises.tsx | 239 ++++++++++++++++++++++++++++++++ src/pages/Home.tsx | 6 + src/pages/Welcome.tsx | 26 ++++ 9 files changed, 542 insertions(+), 153 deletions(-) create mode 100644 src/components/ExerciseItem.tsx create mode 100644 src/pages/Account.tsx create mode 100644 src/pages/Exercises.tsx create mode 100644 src/pages/Welcome.tsx diff --git a/src/App.tsx b/src/App.tsx index b495b3e..50c629f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,21 +4,21 @@ import { IonReactRouter } from "@ionic/react-router" import AppRoutes from "./AppRoutes" import "./index.css" import "@ionic/react/css/core.css" -import Header from "./components/Header" -import Footer from "./components/Footer" +// import Header from "./components/Header" +// import Footer from "./components/Footer" const App: React.FC = () => (
-
+ {/* Контент с отступом равным высоте Header */}
-
+
diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx index 993644f..e21aea8 100644 --- a/src/AppRoutes.tsx +++ b/src/AppRoutes.tsx @@ -2,13 +2,15 @@ import { Route } from "react-router-dom" import Home from "./pages/Home" import Login from "./pages/Login" import Register from "./pages/Register" +import Welcome from "./pages/Welcome" const AppRoutes = () => ( - <> + <> - } /> + +\ ) diff --git a/src/components/ExerciseItem.tsx b/src/components/ExerciseItem.tsx new file mode 100644 index 0000000..36d415e --- /dev/null +++ b/src/components/ExerciseItem.tsx @@ -0,0 +1,154 @@ +"use client" + +import type React from "react" +import { useState } from "react" +import { IonCard, IonCardContent, IonButton, IonIcon, IonBadge } from "@ionic/react" +import { playOutline, videocamOutline, closeCircleOutline } from "ionicons/icons" // Добавлен videocamOutline и closeCircleOutline + +interface Exercise { + id: number + name: string + duration: number // в секундах + description: string + difficulty: "easy" | "medium" | "hard" + completed: boolean + videoUrl?: string // Добавлено поле для URL видео +} + +interface ExerciseItemProps { + exercise: Exercise + onStart: (exercise: Exercise) => void + isCurrent: boolean + isDisabled: boolean + onVideoPlayToggle: (isPlaying: boolean) => void // Добавлено для уведомления родителя +} + +const ExerciseItem: React.FC = ({ exercise, onStart, isCurrent, isDisabled, onVideoPlayToggle }) => { + const [showVideo, setShowVideo] = useState(false) + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` + } + + const getDifficultyColor = (difficulty: string) => { + switch (difficulty) { + case "easy": + return "text-emerald-400" + case "medium": + return "text-yellow-400" + case "hard": + return "text-red-400" + default: + return "text-slate-400" + } + } + + const getDifficultyBg = (difficulty: string) => { + switch (difficulty) { + case "easy": + return "bg-emerald-500/20 border-emerald-500/30" + case "medium": + return "bg-yellow-500/20 border-yellow-500/30" + case "hard": + return "bg-red-500/20 border-red-500/30" + default: + return "bg-slate-500/20 border-slate-500/30" + } + } + + const handleVideoToggle = () => { + setShowVideo((prev) => { + onVideoPlayToggle(!prev) // Уведомляем родителя об изменении состояния видео + return !prev + }) + } + + return ( + + +
+
+

+ {exercise.name} +

+

{exercise.description}

+
+ {exercise.completed && ( + + Выполнено + + )} +
+ + {showVideo && exercise.videoUrl && ( +
+ + + + +
+ )} + +
+
+ {formatTime(exercise.duration)} + + {exercise.difficulty === "easy" ? "Легко" : exercise.difficulty === "medium" ? "Средне" : "Сложно"} + +
+ +
+ {exercise.videoUrl && ( + + + {showVideo ? "Скрыть" : "Видео"} + + )} + + {!exercise.completed && !isCurrent && ( + onStart(exercise)} + className="bg-gradient-to-r from-teal-500 to-cyan-500" + disabled={isDisabled || showVideo} // Отключаем кнопку, если другое упражнение активно или видео проигрывается + > + + Начать + + )} + {isCurrent && ( + + Активно + + )} +
+
+
+
+ ) +} + +export default ExerciseItem diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 4a22a6e..7446102 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,161 +1,40 @@ "use client" -import { useState } from "react" - -interface HeaderProps { - onMenuToggle?: () => void - isMenuOpen?: boolean -} - -function Header({ onMenuToggle, isMenuOpen = false }: HeaderProps) { - const [internalMenuOpen, setInternalMenuOpen] = useState(false) - - const menuOpen = isMenuOpen || internalMenuOpen - - const handleMenuToggle = () => { - if (onMenuToggle) { - onMenuToggle() - } else { - setInternalMenuOpen(!internalMenuOpen) - } - } - +function Header() { return ( -
- {/* Основной контейнер */} -
- {/* Фон с размытием */} -
+
+
+ {/* Верхняя граница с градиентом */} +
- {/* Тонкая нижняя линия */} + {/* Нижняя граница с градиентом */}
{/* Контент */} -
-
- {/* Логотип */} -
- {/* Иконка логотипа */} -
- - - -
- - {/* Текст логотипа */} -
-

- МедРеабилитация -

-

Центр восстановления

-
+
+ {/* Логотип и название */} +
+ {/* Иконка с градиентом */} +
+ + + +
+ {/* Название и описание */} +
+

+ МедРеабилитация +

+

Центр восстановления

- - {/* Меню-бургер */} -
- - {/* Выпадающее меню */} - {menuOpen && ( -
- {/* Фон меню */} -
- - {/* Контент меню */} -
-
- - - {/* Разделитель */} -
- - {/* Дополнительные опции */} -
-
-
- И -
-
-

Иван Петров

-

Пациент

-
-
- -
-
-
-
- )}
) } - -export default Header +export default Header \ No newline at end of file diff --git a/src/index.css b/src/index.css index 7463d24..ccc6f1e 100644 --- a/src/index.css +++ b/src/index.css @@ -1,5 +1,19 @@ @import "tailwindcss"; + +@keyframes gradient { + 0% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } + 100% { background-position: 0% 50%; } +} + +.animate-gradient { + background-size: 300% 300%; + animation: gradient 10s ease infinite; +} + + + /* Ionic переменные для кастомизации */ :root { /* Основные цвета приложения */ diff --git a/src/pages/Account.tsx b/src/pages/Account.tsx new file mode 100644 index 0000000..b2af92b --- /dev/null +++ b/src/pages/Account.tsx @@ -0,0 +1,69 @@ +import type React from "react" +import { IonPage, IonContent, IonList, IonItem, IonLabel, IonIcon, IonToggle, IonButton } from "@ionic/react" +import { + personCircleOutline, + mailOutline, + lockClosedOutline, + notificationsOutline, + moonOutline, + logOutOutline, +} from "ionicons/icons" +import Header from "../components/Header" +import Footer from "../components/Footer" + +const Account: React.FC = () => { + return ( + +
+ +
+
+ Profile +

Himanshi Kashyap

+

himanshi.kashyap@example.com

+
+ + + + + Edit Profile + + + + Change Email + + + + Change Password + + + + + + + Notifications + + + + + Dark Mode + + + + + + + Log Out + +
+
+
+ + ) +} + +export default Account diff --git a/src/pages/Exercises.tsx b/src/pages/Exercises.tsx new file mode 100644 index 0000000..4aeffce --- /dev/null +++ b/src/pages/Exercises.tsx @@ -0,0 +1,239 @@ +"use client" + +import type React from "react" +import { useState, useEffect } from "react" +import { + IonContent, + IonPage, + IonCard, + IonCardContent, + IonCardHeader, + IonCardTitle, + IonButton, + IonIcon, + IonProgressBar, +} from "@ionic/react" +import { playOutline, pauseOutline, stopOutline, timerOutline, fitnessOutline, trophyOutline } from "ionicons/icons" +import ExerciseItem from "../components/ExerciseItem" // Импортируем новый компонент + +interface Exercise { + id: number + name: string + duration: number // в секундах + description: string + difficulty: "easy" | "medium" | "hard" + completed: boolean + videoUrl?: string // Добавлено поле для URL видео +} + +const ExercisesPage: React.FC = () => { + const [currentExercise, setCurrentExercise] = useState(null) + const [timeLeft, setTimeLeft] = useState(0) + const [isRunning, setIsRunning] = useState(false) + const [isPaused, setIsPaused] = useState(false) + const [isAnyVideoPlaying, setIsAnyVideoPlaying] = useState(false) // Новое состояние для отслеживания видео + + const initialExercises: Exercise[] = [ + { + id: 1, + name: "Разминка плечевого сустава", + duration: 300, // 5 минут + description: "Медленные круговые движения плечами для восстановления подвижности", + difficulty: "easy", + completed: false, // Изначально все не выполнено + videoUrl: "/placeholder.svg?height=200&width=300", // Пример видео URL + }, + { + id: 2, + name: "Упражнения для кисти", + duration: 600, // 10 минут + description: "Сжимание и разжимание кулака, вращения кистью", + difficulty: "medium", + completed: false, + videoUrl: "/placeholder.svg?height=200&width=300", // Пример видео URL + }, + { + id: 3, + name: "Растяжка мышц руки", + duration: 450, // 7.5 минут + description: "Статические упражнения на растяжку поврежденных мышц", + difficulty: "hard", + completed: false, + videoUrl: "/placeholder.svg?height=200&width=300", // Пример видео URL + }, + { + id: 4, + name: "Укрепление предплечья", + duration: 400, // 6.6 минут + description: "Упражнения с легкими гантелями для укрепления мышц предплечья", + difficulty: "medium", + completed: false, + videoUrl: "/placeholder.svg?height=200&width=300", // Пример видео URL + }, + { + id: 5, + name: "Координационные упражнения", + duration: 500, // 8.3 минут + description: "Упражнения на баланс и мелкую моторику", + difficulty: "hard", + completed: false, + videoUrl: "/placeholder.svg?height=200&width=300", // Пример видео URL + }, + ] + + const [exerciseList, setExerciseList] = useState(() => { + // Попытка загрузить состояние из localStorage + if (typeof window !== "undefined") { + const savedExercises = localStorage.getItem("rehab_exercises") + if (savedExercises) { + return JSON.parse(savedExercises) + } + } + return initialExercises + }) + + // Сохранение состояния в localStorage при изменении + useEffect(() => { + if (typeof window !== "undefined") { + localStorage.setItem("rehab_exercises", JSON.stringify(exerciseList)) + } + }, [exerciseList]) + + useEffect(() => { + let interval: NodeJS.Timeout | undefined + + if (isRunning && !isPaused && timeLeft > 0) { + interval = setInterval(() => { + setTimeLeft((prevTime) => prevTime - 1) + }, 1000) + } else if (timeLeft === 0 && currentExercise && isRunning) { + // Упражнение завершено + setIsRunning(false) + setCurrentExercise(null) + // Отмечаем упражнение как выполненное + setExerciseList((prev) => prev.map((ex) => (ex.id === currentExercise.id ? { ...ex, completed: true } : ex))) + } + + return () => { + if (interval) clearInterval(interval) + } + }, [isRunning, isPaused, timeLeft, currentExercise]) + + const startExercise = (exercise: Exercise) => { + setCurrentExercise(exercise) + setTimeLeft(exercise.duration) + setIsRunning(true) + setIsPaused(false) + } + + const pauseExercise = () => { + setIsPaused((prev) => !prev) + } + + const stopExercise = () => { + setIsRunning(false) + setIsPaused(false) + setCurrentExercise(null) + setTimeLeft(0) + } + + const formatTime = (seconds: number) => { + const mins = Math.floor(seconds / 60) + const secs = seconds % 60 + return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}` + } + + const completedCount = exerciseList.filter((ex) => ex.completed).length + const totalCount = exerciseList.length + const progressPercentage = totalCount > 0 ? (completedCount / totalCount) * 100 : 0 + + // Обработчик для отслеживания состояния воспроизведения видео + const handleVideoPlayToggle = (isPlaying: boolean) => { + setIsAnyVideoPlaying(isPlaying) + } + + return ( + + +
+ {/* Общий прогресс */} + + + + + Общий прогресс + + + +
+
+ Выполнено упражнений + + {completedCount}/{totalCount} + +
+ +
{progressPercentage.toFixed(0)}% завершено
+
+
+
+ + {/* Текущее упражнение (если активно) */} + {currentExercise && ( + + + + + Текущее упражнение + + + +
+

{currentExercise.name}

+
+
{formatTime(timeLeft)}
+ +
+
+ + + {isPaused ? "Продолжить" : "Пауза"} + + + + Остановить + +
+
+
+
+ )} + + {/* Список упражнений */} +
+

+ + Список упражнений +

+ + {exerciseList.map((exercise) => ( + + ))} +
+
+
+
+ ) +} + +export default ExercisesPage diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index e818b06..2a77d3d 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -16,6 +16,9 @@ import { } from "@ionic/react" import { playOutline, pauseOutline, stopOutline, timerOutline, fitnessOutline, trophyOutline } from "ionicons/icons" +import Header from "../components/Header" +import Footer from "../components/Footer" + interface Exercise { id: number name: string @@ -135,6 +138,7 @@ const Home: React.FC = () => { return ( +
{/* Приветствие и статистика */}
@@ -194,6 +198,7 @@ const Home: React.FC = () => {
+ )} @@ -256,6 +261,7 @@ const Home: React.FC = () => {
+