Обновлены локализации для дней недели и месяцев, добавлены новые строки для выбора даты и существующих уроков. В компоненте формы уроков реализован календарь для выбора даты с учетом существующих лекций.

This commit is contained in:
Primakov Alexandr Alexandrovich 2025-03-26 23:41:05 +03:00
parent 32aad802b9
commit 8a66b96599
7 changed files with 271 additions and 55 deletions

View File

@ -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"
}

View File

@ -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": "Декабрь"
}

View File

@ -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>
);
}}

View File

@ -359,6 +359,10 @@ const LessonList = () => {
onSelectAiSuggestion={handleSelectAiSuggestion}
selectedAiSuggestion={suggestedLessonToCreate}
onRetryAiGeneration={handleRetryAiGeneration}
existingLessons={data?.body?.map(lesson => ({
date: lesson.date,
name: lesson.name
}))}
/>
) : (
<Button

View File

@ -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(); // Создан за день до даты
});

View File

@ -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": "Хакатон",

View File

@ -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"
},