Обновлены локализации для дней недели и месяцев, добавлены новые строки для выбора даты и существующих уроков. В компоненте формы уроков реализован календарь для выбора даты с учетом существующих лекций.
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user