Обновлены локализации для дней недели и месяцев, добавлены новые строки для выбора даты и существующих уроков. В компоненте формы уроков реализован календарь для выбора даты с учетом существующих лекций.
This commit is contained in:
		
							parent
							
								
									32aad802b9
								
							
						
					
					
						commit
						8a66b96599
					
				| @ -217,5 +217,26 @@ | ||||
|     "journal.pl.days.morning": "Morning", | ||||
|     "journal.pl.days.day": "Day", | ||||
|     "journal.pl.days.evening": "Evening", | ||||
|     "journal.pl.lesson.form.selectTime": "Select time" | ||||
|     "journal.pl.lesson.form.selectTime": "Select time", | ||||
|     "journal.pl.lesson.existingLessonHint": "There is already a lesson on this day", | ||||
|     "journal.pl.lesson.form.selectDate": "Select date", | ||||
|     "journal.pl.days.shortMonday": "Mo", | ||||
|     "journal.pl.days.shortTuesday": "Tu", | ||||
|     "journal.pl.days.shortWednesday": "We", | ||||
|     "journal.pl.days.shortThursday": "Th", | ||||
|     "journal.pl.days.shortFriday": "Fr", | ||||
|     "journal.pl.days.shortSaturday": "Sa", | ||||
|     "journal.pl.days.shortSunday": "Su", | ||||
|     "journal.pl.months.january": "January", | ||||
|     "journal.pl.months.february": "February", | ||||
|     "journal.pl.months.march": "March", | ||||
|     "journal.pl.months.april": "April", | ||||
|     "journal.pl.months.may": "May", | ||||
|     "journal.pl.months.june": "June", | ||||
|     "journal.pl.months.july": "July", | ||||
|     "journal.pl.months.august": "August", | ||||
|     "journal.pl.months.september": "September", | ||||
|     "journal.pl.months.october": "October", | ||||
|     "journal.pl.months.november": "November", | ||||
|     "journal.pl.months.december": "December" | ||||
| } | ||||
| @ -214,5 +214,26 @@ | ||||
|   "journal.pl.days.morning": "Утро", | ||||
|   "journal.pl.days.day": "День", | ||||
|   "journal.pl.days.evening": "Вечер", | ||||
|   "journal.pl.lesson.form.selectTime": "Выберите время" | ||||
|   "journal.pl.lesson.form.selectTime": "Выберите время", | ||||
|   "journal.pl.lesson.existingLessonHint": "В этот день уже есть лекция", | ||||
|   "journal.pl.lesson.form.selectDate": "Выберите дату", | ||||
|   "journal.pl.days.shortMonday": "Пн", | ||||
|   "journal.pl.days.shortTuesday": "Вт", | ||||
|   "journal.pl.days.shortWednesday": "Ср", | ||||
|   "journal.pl.days.shortThursday": "Чт", | ||||
|   "journal.pl.days.shortFriday": "Пт", | ||||
|   "journal.pl.days.shortSaturday": "Сб", | ||||
|   "journal.pl.days.shortSunday": "Вс", | ||||
|   "journal.pl.months.january": "Январь", | ||||
|   "journal.pl.months.february": "Февраль", | ||||
|   "journal.pl.months.march": "Март", | ||||
|   "journal.pl.months.april": "Апрель", | ||||
|   "journal.pl.months.may": "Май", | ||||
|   "journal.pl.months.june": "Июнь", | ||||
|   "journal.pl.months.july": "Июль", | ||||
|   "journal.pl.months.august": "Август", | ||||
|   "journal.pl.months.september": "Сентябрь", | ||||
|   "journal.pl.months.october": "Октябрь", | ||||
|   "journal.pl.months.november": "Ноябрь", | ||||
|   "journal.pl.months.december": "Декабрь" | ||||
| } | ||||
| @ -1,4 +1,4 @@ | ||||
| import React, { useEffect } from 'react' | ||||
| import React, { useEffect, useState } from 'react' | ||||
| import { useForm, Controller } from 'react-hook-form' | ||||
| import { | ||||
|   Box, | ||||
| @ -27,9 +27,11 @@ import { | ||||
|   useStyleConfig, | ||||
|   Select, | ||||
|   Wrap, | ||||
|   WrapItem | ||||
|   WrapItem, | ||||
|   IconButton, | ||||
|   Center | ||||
| } from '@chakra-ui/react' | ||||
| import { AddIcon, CheckIcon, WarningIcon, RepeatIcon } from '@chakra-ui/icons' | ||||
| import { AddIcon, CheckIcon, WarningIcon, RepeatIcon, ChevronLeftIcon, ChevronRightIcon } from '@chakra-ui/icons' | ||||
| import { useTranslation } from 'react-i18next' | ||||
| import { FaRobot } from 'react-icons/fa' | ||||
| import dayjs from 'dayjs' | ||||
| @ -58,6 +60,7 @@ interface LessonFormProps { | ||||
|   onSelectAiSuggestion?: (suggestion: any) => void // Обработчик выбора предложения
 | ||||
|   selectedAiSuggestion?: any // Выбранное предложение
 | ||||
|   onRetryAiGeneration?: () => void // Функция для повторного запуска генерации
 | ||||
|   existingLessons?: Array<{ date: string; name: string }> // Добавляем новый проп
 | ||||
| } | ||||
| 
 | ||||
| export const LessonForm = ({ | ||||
| @ -72,7 +75,8 @@ export const LessonForm = ({ | ||||
|   isLoadingAiSuggestions = false, | ||||
|   onSelectAiSuggestion = () => {}, | ||||
|   selectedAiSuggestion, | ||||
|   onRetryAiGeneration = () => {} | ||||
|   onRetryAiGeneration = () => {}, | ||||
|   existingLessons | ||||
| }: LessonFormProps) => { | ||||
|   const { t } = useTranslation() | ||||
|   const isAiSuggested = lesson && !lesson._id && !lesson.id | ||||
| @ -195,6 +199,197 @@ export const LessonForm = ({ | ||||
|     return days[date.getDay()]; | ||||
|   }; | ||||
| 
 | ||||
|   // Добавляем вспомогательные функции для календаря
 | ||||
|   const getDaysInMonth = (year: number, month: number) => { | ||||
|     return new Date(year, month + 1, 0).getDate(); | ||||
|   }; | ||||
| 
 | ||||
|   const getFirstDayOfMonth = (year: number, month: number) => { | ||||
|     return new Date(year, month, 1).getDay(); | ||||
|   }; | ||||
| 
 | ||||
|   const isWeekend = (dayOfWeek: number) => { | ||||
|     return dayOfWeek === 0 || dayOfWeek === 6; // Воскресенье или суббота
 | ||||
|   }; | ||||
| 
 | ||||
|   const isSameDay = (date1: Date, date2: Date) => { | ||||
|     return date1.getFullYear() === date2.getFullYear() && | ||||
|            date1.getMonth() === date2.getMonth() && | ||||
|            date1.getDate() === date2.getDate(); | ||||
|   }; | ||||
|   // Компонент календаря
 | ||||
|   interface CalendarProps { | ||||
|     selectedDate: Date; | ||||
|     onSelectDate: (date: Date) => void; | ||||
|     existingLessons?: string[]; | ||||
|   } | ||||
| 
 | ||||
|   const Calendar: React.FC<CalendarProps> = ({ selectedDate, onSelectDate, existingLessons = [] }) => { | ||||
|     const { t } = useTranslation(); | ||||
|     const [viewDate, setViewDate] = useState(new Date()); | ||||
|      | ||||
|     // Используем короткие названия дней недели из локализации
 | ||||
|     const weekDays = [ | ||||
|       t('journal.pl.days.shortMonday'), | ||||
|       t('journal.pl.days.shortTuesday'), | ||||
|       t('journal.pl.days.shortWednesday'), | ||||
|       t('journal.pl.days.shortThursday'), | ||||
|       t('journal.pl.days.shortFriday'), | ||||
|       t('journal.pl.days.shortSaturday'), | ||||
|       t('journal.pl.days.shortSunday'), | ||||
|     ]; | ||||
| 
 | ||||
|     // Используем локализованные названия месяцев
 | ||||
|     const monthNames = [ | ||||
|       t('journal.pl.months.january'), | ||||
|       t('journal.pl.months.february'), | ||||
|       t('journal.pl.months.march'), | ||||
|       t('journal.pl.months.april'), | ||||
|       t('journal.pl.months.may'), | ||||
|       t('journal.pl.months.june'), | ||||
|       t('journal.pl.months.july'), | ||||
|       t('journal.pl.months.august'), | ||||
|       t('journal.pl.months.september'), | ||||
|       t('journal.pl.months.october'), | ||||
|       t('journal.pl.months.november'), | ||||
|       t('journal.pl.months.december'), | ||||
|     ]; | ||||
| 
 | ||||
|     const daysInMonth = getDaysInMonth(viewDate.getFullYear(), viewDate.getMonth()); | ||||
|     let firstDay = getFirstDayOfMonth(viewDate.getFullYear(), viewDate.getMonth()); | ||||
|     firstDay = firstDay === 0 ? 6 : firstDay - 1; // Корректируем для начала недели с понедельника
 | ||||
| 
 | ||||
|     const days = Array.from({ length: 42 }, (_, i) => { | ||||
|       const dayNumber = i - firstDay + 1; | ||||
|       if (dayNumber > 0 && dayNumber <= daysInMonth) { | ||||
|         const date = new Date(viewDate.getFullYear(), viewDate.getMonth(), dayNumber); | ||||
|         return { | ||||
|           date, | ||||
|           dayOfMonth: dayNumber, | ||||
|           isCurrentMonth: true, | ||||
|           isWeekend: isWeekend(date.getDay()), | ||||
|           isToday: isSameDay(date, new Date()), | ||||
|           isSelected: isSameDay(date, selectedDate) | ||||
|         }; | ||||
|       } | ||||
|       return null; | ||||
|     }); | ||||
| 
 | ||||
|     // Добавим функцию проверки наличия лекции в определенный день
 | ||||
|     const hasLessonOnDate = (date: Date) => { | ||||
|       return existingLessons.some(lessonDate =>  | ||||
|         isSameDay(new Date(lessonDate), date) | ||||
|       ); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|       <Box> | ||||
|         <Text fontSize="sm" mb={2}>{t('journal.pl.lesson.form.selectDate')}</Text> | ||||
|         <HStack justify="space-between" mb={2}> | ||||
|           <IconButton | ||||
|             aria-label="Previous month" | ||||
|             icon={<ChevronLeftIcon />} | ||||
|             size="sm" | ||||
|             onClick={() => { | ||||
|               const newDate = new Date(viewDate); | ||||
|               newDate.setMonth(newDate.getMonth() - 1); | ||||
|               setViewDate(newDate); | ||||
|             }} | ||||
|           /> | ||||
|           <HStack> | ||||
|             <Select | ||||
|               size="sm" | ||||
|               value={viewDate.getMonth()} | ||||
|               onChange={(e) => { | ||||
|                 const newDate = new Date(viewDate); | ||||
|                 newDate.setMonth(parseInt(e.target.value)); | ||||
|                 setViewDate(newDate); | ||||
|               }} | ||||
|             > | ||||
|               {monthNames.map((month, i) => ( | ||||
|                 <option key={i} value={i}>{month}</option> | ||||
|               ))} | ||||
|             </Select> | ||||
|             <Select | ||||
|               size="sm" | ||||
|               value={viewDate.getFullYear()} | ||||
|               onChange={(e) => { | ||||
|                 const newDate = new Date(viewDate); | ||||
|                 newDate.setFullYear(parseInt(e.target.value)); | ||||
|                 setViewDate(newDate); | ||||
|               }} | ||||
|             > | ||||
|               {Array.from({ length: 5 }, (_, i) => { | ||||
|                 const year = new Date().getFullYear() + i; | ||||
|                 return <option key={year} value={year}>{year}</option>; | ||||
|               })} | ||||
|             </Select> | ||||
|           </HStack> | ||||
|           <IconButton | ||||
|             aria-label="Next month" | ||||
|             icon={<ChevronRightIcon />} | ||||
|             size="sm" | ||||
|             onClick={() => { | ||||
|               const newDate = new Date(viewDate); | ||||
|               newDate.setMonth(newDate.getMonth() + 1); | ||||
|               setViewDate(newDate); | ||||
|             }} | ||||
|           /> | ||||
|         </HStack> | ||||
| 
 | ||||
|         <SimpleGrid columns={7} spacing={1}> | ||||
|           {weekDays.map(day => ( | ||||
|             <Center key={day} py={1}> | ||||
|               <Text fontSize="xs" color="gray.500"> | ||||
|                 {day} | ||||
|               </Text> | ||||
|             </Center> | ||||
|           ))} | ||||
|           {days.map((day, i) => { | ||||
|             const hasLesson = day?.isCurrentMonth && hasLessonOnDate(day.date); | ||||
|              | ||||
|             return ( | ||||
|               <Button | ||||
|                 key={i} | ||||
|                 size="sm" | ||||
|                 variant={day?.isSelected ? "solid" : "ghost"} | ||||
|                 colorScheme={day?.isSelected ? "blue" : day?.isWeekend ? "red" : "gray"} | ||||
|                 opacity={day?.isCurrentMonth ? 1 : 0} | ||||
|                 onClick={() => day?.date && onSelectDate(day.date)} | ||||
|                 h="32px" | ||||
|                 disabled={!day?.isCurrentMonth} | ||||
|                 position="relative" | ||||
|                 _after={hasLesson ? { | ||||
|                   content: '""', | ||||
|                   position: "absolute", | ||||
|                   bottom: "2px", | ||||
|                   left: "50%", | ||||
|                   transform: "translateX(-50%)", | ||||
|                   width: "4px", | ||||
|                   height: "4px", | ||||
|                   borderRadius: "full", | ||||
|                   bg: day?.isSelected ? "white" : "blue.500", | ||||
|                   _dark: { | ||||
|                     bg: day?.isSelected ? "white" : "blue.300" | ||||
|                   } | ||||
|                 } : undefined} | ||||
|                 title={hasLesson ? t('journal.pl.lesson.existingLessonHint') : undefined} | ||||
|               > | ||||
|                 <Text | ||||
|                   fontSize="xs" | ||||
|                   fontWeight={day?.isToday ? "bold" : "normal"} | ||||
|                   textDecoration={day?.isToday ? "underline" : "none"} | ||||
|                 > | ||||
|                   {day?.dayOfMonth} | ||||
|                 </Text> | ||||
|               </Button> | ||||
|             ); | ||||
|           })} | ||||
|         </SimpleGrid> | ||||
|       </Box> | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <Card align="left" bg={isAiSuggested ? aiHighlightColor : undefined}> | ||||
|       <CardHeader display="flex"> | ||||
| @ -225,57 +420,33 @@ export const LessonForm = ({ | ||||
|               name="date" | ||||
|               rules={{ required: t('journal.pl.common.required') }} | ||||
|               render={({ field }) => { | ||||
|                 // Разделяем текущее значение на дату и время
 | ||||
|                 const [currentDate = '', currentTime = '00:00:00'] = field.value.split('T'); | ||||
|                 // Получаем часы и минуты без секунд для сравнения
 | ||||
|                 const currentTimeShort = currentTime.split(':').slice(0, 2).join(':'); | ||||
|                 const selectedDate = new Date(currentDate); | ||||
|                  | ||||
|                 // Получаем существующие лекции из пропсов компонента
 | ||||
|                 const existingLessons2 = existingLessons?.map(lesson => lesson.date) || []; | ||||
|                  | ||||
|                 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" | ||||
|                       /> | ||||
|                     <SimpleGrid columns={{ base: 1, md: 2 }} spacing={4}> | ||||
|                       {/* Календарь */} | ||||
|                       <Box> | ||||
|                         <Calendar | ||||
|                           selectedDate={selectedDate} | ||||
|                           existingLessons={existingLessons2} | ||||
|                           onSelectDate={(date) => { | ||||
|                             const formattedDate = dateToCalendarFormat(date.toISOString()).split('T')[0]; | ||||
|                             field.onChange(`${formattedDate}T${currentTimeShort}:00`); | ||||
|                           }} | ||||
|                         /> | ||||
|                       </Box> | ||||
| 
 | ||||
|                       {/* Временные слоты */} | ||||
|                       <Box> | ||||
|                         <Text fontSize="sm" mb={2}>{t('journal.pl.lesson.form.selectTime')}:</Text> | ||||
|                         <SimpleGrid columns={{ base: 1, md: 3 }} spacing={4}> | ||||
|                         <SimpleGrid columns={1} spacing={4}> | ||||
|                           {Object.entries(timeGroups).map(([groupName, slots]) => ( | ||||
|                             <Box key={groupName}> | ||||
|                               <Text fontSize="xs" color="gray.500" mb={1}> | ||||
| @ -284,7 +455,6 @@ export const LessonForm = ({ | ||||
|                               <Wrap spacing={1}> | ||||
|                                 {slots.map(slot => { | ||||
|                                   const isSelected = currentTimeShort === slot; | ||||
|                                    | ||||
|                                   return ( | ||||
|                                     <WrapItem key={slot}> | ||||
|                                       <Button | ||||
| @ -307,7 +477,7 @@ export const LessonForm = ({ | ||||
|                           ))} | ||||
|                         </SimpleGrid> | ||||
|                       </Box> | ||||
|                     </VStack> | ||||
|                     </SimpleGrid> | ||||
|                   </FormControl> | ||||
|                 ); | ||||
|               }} | ||||
|  | ||||
| @ -359,6 +359,10 @@ const LessonList = () => { | ||||
|                 onSelectAiSuggestion={handleSelectAiSuggestion} | ||||
|                 selectedAiSuggestion={suggestedLessonToCreate} | ||||
|                 onRetryAiGeneration={handleRetryAiGeneration} | ||||
|                 existingLessons={data?.body?.map(lesson => ({ | ||||
|                   date: lesson.date, | ||||
|                   name: lesson.name | ||||
|                 }))} | ||||
|               /> | ||||
|             ) : ( | ||||
|               <Button | ||||
|  | ||||
| @ -35,7 +35,7 @@ function readAndModifyJson(filePath) { | ||||
|       jsonContent.body.forEach((lesson) => { | ||||
|         // Случайная дата в пределах последних 3 месяцев
 | ||||
|         const randomDate = new Date(); | ||||
|         randomDate.setMonth(randomDate.getMonth() - Math.random() * 3); | ||||
|         randomDate.setDate(randomDate.getDate() - Math.random() * 30); | ||||
|         lesson.date = randomDate.toISOString(); | ||||
|         lesson.created = new Date(randomDate.getTime() - 86400000).toISOString(); // Создан за день до даты
 | ||||
|       }); | ||||
|  | ||||
| @ -590,8 +590,8 @@ | ||||
|             "sub": "developer", | ||||
|             "email": "email@email.ml" | ||||
|         }, | ||||
|         "startDt": "2024-08-25T17:40:17.814Z", | ||||
|         "created": "2024-08-25T17:40:17.814Z", | ||||
|         "startDt": "2024-08-25T17:30:00.000Z", | ||||
|         "created": "2024-08-25T17:40:17.000Z", | ||||
|         "examWithJury2": { | ||||
|             "_id": "66cf3d3f4637d420d6271451", | ||||
|             "name": "Хакатон", | ||||
|  | ||||
| @ -19,7 +19,7 @@ | ||||
|                         "email": "primakovpro@gmail.com" | ||||
|                     } | ||||
|                 ], | ||||
|                 "date": "2024-04-16T13:38:00.000Z", | ||||
|                 "date": "2024-04-16T13:30:00.000Z", | ||||
|                 "created": "2024-04-16T13:38:23.381Z", | ||||
|                 "id": "661e7f4f69f40b0ebebcd5e4" | ||||
|             }, | ||||
| @ -37,7 +37,7 @@ | ||||
|                         "email": "primakovpro@gmail.com" | ||||
|                     } | ||||
|                 ], | ||||
|                 "date": "2024-08-04T07:00:00.000Z", | ||||
|                 "date": "2024-08-04T08:00:00.000Z", | ||||
|                 "created": "2024-08-04T06:23:28.491Z", | ||||
|                 "id": "66af1e60a0eef5a89f99aa94" | ||||
|             }, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user