From 32aad802b96362d5b8fe81d9009ee8f32dd1fa9c Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Alexandrovich <primakov.pro@yandex.ru> Date: Wed, 26 Mar 2025 23:20:25 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D1=8B=20=D0=BD=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D0=B5=20=D1=81=D0=BB=D0=BE?= =?UTF-8?q?=D1=82=D1=8B=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=84=D0=BE=D1=80=D0=BC=D0=B0=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=B0=20=D0=B4=D0=B0=D1=82=D1=8B=20=D0=B8=20?= =?UTF-8?q?=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D1=83=D1=80=D0=BE=D0=BA=D0=BE=D0=B2.=20=D0=A0=D0=B5=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D1=8B=20=D1=84=D1=83?= =?UTF-8?q?=D0=BD=D0=BA=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=B3?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D1=81=D0=BB=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BB=D0=B5=D0=B4=D1=83=D1=8E?= =?UTF-8?q?=D1=89=D0=B5=D0=B3=D0=BE=20=D0=B4=D0=BE=D1=81=D1=82=D1=83=D0=BF?= =?UTF-8?q?=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=8B=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20=D0=BD=D0=BE=D0=B2=D1=8B?= =?UTF-8?q?=D1=85=20=D1=81=D1=82=D1=80=D0=BE=D0=BA.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.json | 9 +- locales/ru.json | 9 +- .../lesson-list/components/lessons-form.tsx | 170 ++++++++++++++++-- 3 files changed, 168 insertions(+), 20 deletions(-) diff --git a/locales/en.json b/locales/en.json index 97c4950..e086347 100644 --- a/locales/en.json +++ b/locales/en.json @@ -210,5 +210,12 @@ "journal.pl.overview.new": "new", "journal.pl.overview.pastLessonsStats": "Statistics of past lessons", "journal.pl.overview.dayOfWeekHelp": "Only statistics for completed lessons are shown", - "journal.pl.overview.attendanceHelp": "Attendance is calculated based on past lessons only" + "journal.pl.overview.attendanceHelp": "Attendance is calculated based on past lessons only", + "journal.pl.today": "Today", + "journal.pl.tomorrow": "Tomorrow", + "journal.pl.dayAfterTomorrow": "Day after tomorrow", + "journal.pl.days.morning": "Morning", + "journal.pl.days.day": "Day", + "journal.pl.days.evening": "Evening", + "journal.pl.lesson.form.selectTime": "Select time" } \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index bcb7d40..0e36fd4 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -207,5 +207,12 @@ "journal.pl.overview.new": "новых", "journal.pl.overview.pastLessonsStats": "Статистика проведённых занятий", "journal.pl.overview.dayOfWeekHelp": "Показана статистика только состоявшихся занятий", - "journal.pl.overview.attendanceHelp": "Посещаемость рассчитана только по прошедшим занятиям" + "journal.pl.overview.attendanceHelp": "Посещаемость рассчитана только по прошедшим занятиям", + "journal.pl.today": "Сегодня", + "journal.pl.tomorrow": "Завтра", + "journal.pl.dayAfterTomorrow": "Послезавтра", + "journal.pl.days.morning": "Утро", + "journal.pl.days.day": "День", + "journal.pl.days.evening": "Вечер", + "journal.pl.lesson.form.selectTime": "Выберите время" } \ No newline at end of file diff --git a/src/pages/lesson-list/components/lessons-form.tsx b/src/pages/lesson-list/components/lessons-form.tsx index acdf034..6363a4b 100644 --- a/src/pages/lesson-list/components/lessons-form.tsx +++ b/src/pages/lesson-list/components/lessons-form.tsx @@ -24,7 +24,10 @@ import { SimpleGrid, Skeleton, SkeletonText, - useStyleConfig + useStyleConfig, + Select, + Wrap, + WrapItem } from '@chakra-ui/react' import { AddIcon, CheckIcon, WarningIcon, RepeatIcon } from '@chakra-ui/icons' import { useTranslation } from 'react-i18next' @@ -39,6 +42,7 @@ import { ErrorSpan } from '../style' interface NewLessonForm { name: string date: string + time: string } interface LessonFormProps { @@ -131,6 +135,66 @@ export const LessonForm = ({ onSelectAiSuggestion(suggestion) } + // Добавляем новые вспомогательные функции + const generateTimeSlots = () => { + const slots = []; + for (let hour = 8; hour <= 21; hour++) { + slots.push(`${hour.toString().padStart(2, '0')}:00`); + slots.push(`${hour.toString().padStart(2, '0')}:30`); + } + return slots; + }; + + const getNextTimeSlots = (date: string, count: number = 3) => { + const currentDate = new Date(); + const selectedDate = new Date(date); + const isToday = selectedDate.toDateString() === currentDate.toDateString(); + + if (!isToday) return []; + + const currentMinutes = currentDate.getHours() * 60 + currentDate.getMinutes(); + const slots = generateTimeSlots(); + + return slots + .map(slot => { + const [hours, minutes] = slot.split(':').map(Number); + const slotMinutes = hours * 60 + minutes; + return { slot, minutes: slotMinutes }; + }) + .filter(({ minutes }) => minutes > currentMinutes) + .slice(0, count) + .map(({ slot }) => slot); + }; + + const timeGroups = { + [`${t('journal.pl.days.morning')} (8-12)`]: generateTimeSlots().filter(slot => { + const hour = parseInt(slot.split(':')[0]); + return hour >= 8 && hour < 12; + }), + [`${t('journal.pl.days.day')} (12-17)`]: generateTimeSlots().filter(slot => { + const hour = parseInt(slot.split(':')[0]); + return hour >= 12 && hour < 17; + }), + [`${t('journal.pl.days.evening')} (17-21)`]: generateTimeSlots().filter(slot => { + const hour = parseInt(slot.split(':')[0]); + return hour >= 17 && hour <= 21; + }) + }; + + // Добавляем функцию для получения дня недели + const getDayOfWeek = (date: Date) => { + const days = [ + t('journal.pl.days.sunday'), + t('journal.pl.days.monday'), + t('journal.pl.days.tuesday'), + t('journal.pl.days.wednesday'), + t('journal.pl.days.thursday'), + t('journal.pl.days.friday'), + t('journal.pl.days.saturday') + ]; + return days[date.getDay()]; + }; + return ( <Card align="left" bg={isAiSuggested ? aiHighlightColor : undefined}> <CardHeader display="flex"> @@ -160,23 +224,93 @@ export const LessonForm = ({ control={control} name="date" rules={{ required: t('journal.pl.common.required') }} - render={({ field }) => ( - <FormControl> - <FormLabel>{t('journal.pl.lesson.form.date')}</FormLabel> - <Input - {...field} - required={false} - placeholder={t('journal.pl.lesson.form.datePlaceholder')} - size="md" - type="datetime-local" - /> - {errors.date ? ( - <FormErrorMessage>{errors.date?.message}</FormErrorMessage> - ) : ( - <FormHelperText>{t('journal.pl.lesson.form.dateTime')}</FormHelperText> - )} - </FormControl> - )} + render={({ field }) => { + // Разделяем текущее значение на дату и время + const [currentDate = '', currentTime = '00:00:00'] = field.value.split('T'); + // Получаем часы и минуты без секунд для сравнения + const currentTimeShort = currentTime.split(':').slice(0, 2).join(':'); + + return ( + <FormControl> + <FormLabel>{t('journal.pl.lesson.form.date')}</FormLabel> + <VStack align="stretch" spacing={4}> + <HStack spacing={2}> + {[0, 1, 2].map(daysToAdd => { + const date = new Date(); + date.setDate(date.getDate() + daysToAdd); + const formattedDate = dateToCalendarFormat(date.toISOString()).split('T')[0]; + const dayOfWeek = getDayOfWeek(date); + + return ( + <Button + key={daysToAdd} + size="sm" + variant={currentDate === formattedDate ? "solid" : "outline"} + colorScheme="blue" + onClick={() => { + // Сохраняем текущее время при смене даты + field.onChange(`${formattedDate}T${currentTime}:00`); + }} + > + {daysToAdd === 0 ? t('journal.pl.today') : + daysToAdd === 1 ? t('journal.pl.tomorrow') : + t('journal.pl.dayAfterTomorrow')} + <Text as="span" fontSize="xs" ml={1} color="gray.500"> + ({dayOfWeek}) + </Text> + </Button> + ); + })} + </HStack> + + <Input + value={currentDate} + onChange={(e) => { + // При ручном изменении даты сохраняем текущее время + field.onChange(`${e.target.value}T${currentTime}:00`); + }} + type="date" + size="sm" + /> + + <Box> + <Text fontSize="sm" mb={2}>{t('journal.pl.lesson.form.selectTime')}:</Text> + <SimpleGrid columns={{ base: 1, md: 3 }} spacing={4}> + {Object.entries(timeGroups).map(([groupName, slots]) => ( + <Box key={groupName}> + <Text fontSize="xs" color="gray.500" mb={1}> + {groupName} + </Text> + <Wrap spacing={1}> + {slots.map(slot => { + const isSelected = currentTimeShort === slot; + + return ( + <WrapItem key={slot}> + <Button + size="xs" + variant={isSelected ? "solid" : "outline"} + colorScheme="blue" + onClick={() => { + field.onChange(`${currentDate}T${slot}:00`); + }} + h="24px" + minW="54px" + > + {slot} + </Button> + </WrapItem> + ); + })} + </Wrap> + </Box> + ))} + </SimpleGrid> + </Box> + </VStack> + </FormControl> + ); + }} /> <Controller