Skip to content

Commit

Permalink
fix typing bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
SecurityQQ committed Jun 6, 2024
1 parent 5b2db4c commit 1613fc9
Show file tree
Hide file tree
Showing 13 changed files with 419 additions and 109 deletions.
32 changes: 32 additions & 0 deletions src/assets/recommendations.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"recommendations": [
"Сегодня первый день твоих месячных, дорогая. Побалуй себя горячей ванной и шоколадом! 🍫",
"Сегодня комфортный день: надень уютную одежду, смотри сериалы и расслабься",
"Сегодня день йоги и смузи! Легкая растяжка и вкусный напиток со шпинатом 🧘‍♀️",
"Сегодня день фильма! Укутайся в одеяло, смотри кино и ешь попкорн 🍿",
"Последний день! Сделай маникюр, маску для лица и порадуй себя уходом 💅",
"Сегодня активный день! Прогулка в парке или танцевальный класс для энергии 🌳",
"Сегодня начни хобби или проект, ешь овощи и белок для энергии",
"Сегодня побалуй себя десертом и любимым занятием, расслабься и наслаждайся 🍰",
"Сегодня спа-день! Увлажняющая маска и сияющая кожа, наслаждайся ✨",
"Сегодня день красоты! Нарядись, даже если для себя, планируй свидание 💃",
"Сегодня тренировка! Чувствуй себя сильной и ешь антиоксиданты 🏋️‍♀️",
"Сегодня творчество! Пиши, рисуй, занимайся чем-то вдохновляющим 🎨",
"Сегодня расслабься! Массаж или ванна со свечами перед овуляцией 🛀",
"Сегодня овуляция! Наслаждайся романтикой или сольным удовольствием ❤️",
"Сегодня йога! Сохрани спокойствие и балансируй свои эмоции 🧘‍♀️",
"Сегодня чай и клетчатка! Пей травяной чай и ешь пищу для снятия вздутия ☕",
"Сегодня уход за кожей! Увлажняющая маска и много воды для сияния 💧",
"Сегодня медитация! Практикуй медитацию или глубокое дыхание для спокойствия 🧘",
"Сегодня ужин! Встречайся с друзьями или семьей для поднятия настроения 🍽️",
"Сегодня творчество! Займись рукоделием или декором для креативности ✂️",
"Сегодня отдых! Слушай тело, отдохни с хорошей книгой или фильмом 📚",
"Сегодня суп! Ешь сытный суп или рагу для утешения и питательной ценности 🍲",
"Сегодня ванна! Расслабляющая ванна с лавандой для спокойствия 🛀",
"Сегодня дневник! Ведение дневника или прогулка на природе для гармонии 🌿",
"Сегодня подготовка! Запасись любимыми менструальными продуктами",
"Сегодня легкая йога! Упражнения и гидратация для снятия предменструальных болей 🧘‍♀️",
"Сегодня уют! Расслабься дома с теплым напитком и любимым шоу ☕",
"Сегодня уход за собой! Размышляй и ставь цели на следующий месяц 🌟"
]
}
190 changes: 135 additions & 55 deletions src/components/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,73 +1,153 @@
import React from 'react';
import React, { useState, useEffect } from 'react';

import MiniCalendar from './MiniCalendar';

type CalendarProps = {
lastMenstruationDate: Date;
cycleLength: number;
mode: 'mini' | 'full';
isEditing: boolean;
onEdit: () => void;
onSave: (date: Date) => Promise<void>;
};

const Calendar: React.FC<CalendarProps> = ({ lastMenstruationDate, cycleLength, mode }) => {
const Calendar: React.FC<CalendarProps> = ({
lastMenstruationDate,
cycleLength,
mode,
isEditing,
onEdit,
onSave,
}) => {
const [months, setMonths] = useState<Date[]>([]);
const [currentPeriodDate, setCurrentPeriodDate] = useState(lastMenstruationDate);

useEffect(() => {
const initialMonths = [];
for (let i = -1; i <= 1; i++) {
initialMonths.push(new Date(new Date().getFullYear(), new Date().getMonth() + i, 1));
}
setMonths(initialMonths);
}, []);

const loadPreviousMonth = () => {
setMonths((prev) => [new Date(prev[0].getFullYear(), prev[0].getMonth() - 1, 1), ...prev]);
};

const loadNextMonth = () => {
setMonths((prev) => [
...prev,
new Date(prev[prev.length - 1].getFullYear(), prev[prev.length - 1].getMonth() + 1, 1),
]);
};

const daysInMonth = (date: Date) =>
new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();

const generateDates = (month: Date) => {
const startOfMonth = new Date(month.getFullYear(), month.getMonth(), 1);
const endOfMonth = new Date(month.getFullYear(), month.getMonth() + 1, 0);
const daysInPrevMonth = daysInMonth(new Date(month.getFullYear(), month.getMonth() - 1));
const startDay = startOfMonth.getDay();
const daysInCurrentMonth = daysInMonth(month);

const dates = [];

// Fill dates from previous month
for (let i = startDay - 1; i >= 0; i--) {
dates.push(new Date(month.getFullYear(), month.getMonth() - 1, daysInPrevMonth - i));
}

// Fill dates from current month
for (let i = 1; i <= daysInCurrentMonth; i++) {
dates.push(new Date(month.getFullYear(), month.getMonth(), i));
}

// Fill dates from next month
const nextMonthDays = 42 - dates.length; // 42 to fill the calendar grid
for (let i = 1; i <= nextMonthDays; i++) {
dates.push(new Date(month.getFullYear(), month.getMonth() + 1, i));
}

return dates;
};

const today = new Date();
const daysSinceLastPeriod = Math.floor(
(new Date().getTime() - new Date(lastMenstruationDate).getTime()) / (1000 * 60 * 60 * 24)
(today.getTime() - new Date(currentPeriodDate).getTime()) / (1000 * 60 * 60 * 24)
);

const today = new Date();
let dates = [];
const isFutureDate = (date: Date) => date > today;

const handleDateClick = async (date: Date) => {
if (isEditing) {
setCurrentPeriodDate(date);
await onSave(date);
}
};

if (mode === 'mini') {
dates = Array.from({ length: 7 }, (_, index) => {
const date = new Date(today);
date.setDate(today.getDate() + index);
return date;
});
} else {
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1);
const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
const daysInMonth = endOfMonth.getDate();

dates = Array.from({ length: daysInMonth }, (_, index) => {
const date = new Date(startOfMonth);
date.setDate(startOfMonth.getDate() + index);
return date;
});
return <MiniCalendar lastMenstruationDate={currentPeriodDate} cycleLength={cycleLength} />;
}

const dayNames = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];

return (
<div className="mt-4 flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
{dayNames.map((day, index) => (
<span key={index} className="w-8 text-center text-gray-400">
{day}
</span>
))}
</div>
<div className={`grid ${mode === 'mini' ? 'grid-cols-7' : 'grid-cols-7 gap-1'}`}>
{dates.map((date, index) => {
const cycleDay =
(daysSinceLastPeriod + (mode === 'mini' ? index : date.getDate() - 1)) % cycleLength;
const isOvulation = cycleDay >= 12 && cycleDay <= 17;
const isPeriod = cycleDay >= 1 && cycleDay <= 5;
const isToday = date.toDateString() === today.toDateString();

return (
<div key={index} className="flex flex-col items-center">
<div
className={`flex size-8 items-center justify-center rounded-full ${
isToday
? 'bg-gradient-to-b from-[#A3CFFF] to-[#3290F8] text-white'
: isPeriod
? 'bg-gradient-to-b from-[#FFA3A3] to-[#F83232] text-white'
: isOvulation
? 'bg-gradient-to-b from-[#A3FFA3] to-[#32F832] text-white'
: 'bg-transparent'
} ${isToday ? 'text-white' : isOvulation ? 'text-white' : 'text-gray-400'}`}>
{date.getDate()}
</div>
</div>
);
})}
<div className="flex h-full flex-col items-center gap-2 overflow-y-scroll">
{months.map((month, index) => (
<div key={index} className="w-full px-4">
<header className="flex w-full items-center justify-center bg-white py-2">
<h1 className="text-lg font-semibold">
{month.toLocaleString('default', { month: 'long', year: 'numeric' })}
</h1>
</header>

<div className="grid w-full grid-cols-7 gap-1">
{generateDates(month).map((date, index) => {
const isCurrentMonth = date.getMonth() === month.getMonth();
const daysFromStartOfCycle =
Math.floor(
(date.getTime() - new Date(currentPeriodDate).getTime()) / (1000 * 60 * 60 * 24)
) % cycleLength;
const isPeriod = daysFromStartOfCycle >= 0 && daysFromStartOfCycle < 5;
const isOvulation = daysFromStartOfCycle >= 12 && daysFromStartOfCycle < 17;
const isToday = date.toDateString() === today.toDateString();
const futurePeriod = isPeriod && isFutureDate(date);
const futureOvulation = isOvulation && isFutureDate(date);
const showPeriod = isPeriod && date >= currentPeriodDate;
const showOvulation = isOvulation && date >= currentPeriodDate;

return (
<div key={index} className="flex flex-col items-center">
<div
className={`flex size-10 items-center justify-center rounded-full ${
isEditing
? 'border-2 border-gray-300'
: showPeriod
? futurePeriod
? 'border-2 border-dashed border-[#F67DBE]'
: 'bg-gradient-to-b from-[#E494BF4D] to-[#F67DBE]'
: showOvulation
? futureOvulation
? 'border-2 border-dashed border-[#3290F8]'
: 'bg-gradient-to-b from-[#A3CFFF] to-[#3290F8]'
: isToday
? 'bg-[#DCF2FF] font-bold'
: 'bg-transparent'
} ${!isCurrentMonth ? 'text-gray-400' : 'text-black'}`}
onClick={() => handleDateClick(date)}>
{date.getDate()}
</div>
</div>
);
})}
</div>
</div>
))}
<div className="fixed bottom-16 mb-4 flex w-full justify-center">
<button
className="flex h-9 w-64 items-center justify-center rounded-full bg-[#007AFF] p-0 text-sm font-semibold text-white"
onClick={isEditing ? () => onSave(currentPeriodDate) : onEdit}>
{isEditing ? 'СОХРАНИТЬ' : 'ИЗМЕНИТЬ ДАТЫ МЕСЯЧНЫХ'}
</button>
</div>
</div>
);
Expand Down
18 changes: 18 additions & 0 deletions src/components/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';

interface LoaderProps {
text?: string;
}

const Loader: React.FC<LoaderProps> = ({ text = 'Loading...' }) => {
return (
<div className="flex h-screen items-center justify-center bg-white">
<div className="flex flex-col items-center">
<img src="/logo.png" alt="Loading" className="size-24 animate-spin" />
<p className="mt-4 text-blue-500">{text}</p>
</div>
</div>
);
};

export default Loader;
90 changes: 90 additions & 0 deletions src/components/MiniCalendar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import React from 'react';

type MiniCalendarProps = {
lastMenstruationDate: Date;
cycleLength: number;
};

const MiniCalendar: React.FC<MiniCalendarProps> = ({ lastMenstruationDate, cycleLength }) => {
const today = new Date();
const daysSinceLastPeriod = Math.floor(
(today.getTime() - new Date(lastMenstruationDate).getTime()) / (1000 * 60 * 60 * 24)
);

const generateNext7Days = () => {
const dates = [];
for (let i = 0; i < 7; i++) {
const date = new Date(today);
date.setDate(today.getDate() + i);
dates.push(date);
}
return dates;
};

const dates = generateNext7Days();

const dayNames = ['П', 'Вт', 'Ср', 'Че', 'П', 'Сб', 'Во'];

const getOrderedDayNames = () => {
const startIndex = (today.getDay() + 6) % 7; // getDay() returns 0 for Sunday, so adjust
const orderedDays = [...dayNames.slice(startIndex), ...dayNames.slice(0, startIndex)];
return orderedDays;
};

const orderedDayNames = getOrderedDayNames();

return (
<div className="mt-4 flex flex-col items-center gap-4">
<div className="flex w-full justify-between px-4">
{orderedDayNames.map((day, index) => {
const daysFromStartOfCycle = (daysSinceLastPeriod + index) % cycleLength;
const isPeriod = daysFromStartOfCycle >= 0 && daysFromStartOfCycle < 5;
const isToday = index === 0; // First element corresponds to today

return (
<span
key={index}
className={`w-10 text-center text-sm ${
isPeriod
? isToday
? 'text-[#F76F45]'
: 'text-[#F76F45]'
: isToday
? 'text-[#007AFF]'
: 'text-[#65C7FF]'
}`}>
{isToday ? 'СЕГОДНЯ' : day}
</span>
);
})}
</div>
<div className="grid w-full grid-cols-7 gap-6 px-4">
{dates.map((date, index) => {
const daysFromStartOfCycle = (daysSinceLastPeriod + index) % cycleLength;
const isPeriod = daysFromStartOfCycle >= 0 && daysFromStartOfCycle < 5;
const isOvulation = daysFromStartOfCycle >= 12 && daysFromStartOfCycle < 17;
const isToday = date.toDateString() === today.toDateString();

return (
<div key={index} className="flex flex-col items-center">
<div
className={`flex size-10 items-center justify-center rounded-full ${
isPeriod
? 'bg-[#FFD3C6]'
: isOvulation
? 'bg-gradient-to-b from-[#A3CFFF] to-[#3290F8]'
: isToday
? 'bg-[#DCF2FF] font-bold text-[#007AFF]'
: 'bg-transparent'
} ${!isPeriod && !isOvulation && !isToday ? 'text-[#65C7FF]' : 'text-black'}`}>
{date.getDate()}
</div>
</div>
);
})}
</div>
</div>
);
};

export default MiniCalendar;
Loading

0 comments on commit 1613fc9

Please sign in to comment.