diff --git a/apps/xi.web/src/index.css b/apps/xi.web/src/index.css index 45224f3b..b0f0bd65 100644 --- a/apps/xi.web/src/index.css +++ b/apps/xi.web/src/index.css @@ -36,6 +36,7 @@ @source "../../../packages/pages.email/src"; @source "../../../packages/pages.notes/src"; @source "../../../packages/pages.email-confirm/src"; +@source "../../../packages/features.lesson.add/src"; @source "../../../node_modules/@xipkg"; diff --git a/packages/features.lesson.add/README.md b/packages/features.lesson.add/README.md new file mode 100644 index 00000000..9d07398e --- /dev/null +++ b/packages/features.lesson.add/README.md @@ -0,0 +1,30 @@ +# Модуль создания счетов на оплату (features.invoice) + +Модуль предназначен для создания и отправки счетов на оплату в приложении. + +## Структура модуля + +``` +src/ +├── hooks/ - хуки для работы с формой счета и утилитарные хуки +├── model/ - схемы и вспомогательные функции для формы счета +├── types/ - типы TypeScript для модуля +├── ui/ - React-компоненты UI (InvoiceModal, InputWithHelper и др.) +├── locales/ - файлы локализации +``` + +## Использование + +```tsx +import { InvoiceModal } from 'features.invoice'; + +export default function Page() { + const [open, setOpen] = useState(false); + return ( + <> + + + + ); +} +``` diff --git a/packages/features.lesson.add/eslint.config.js b/packages/features.lesson.add/eslint.config.js new file mode 100644 index 00000000..15768bd7 --- /dev/null +++ b/packages/features.lesson.add/eslint.config.js @@ -0,0 +1,3 @@ +import config from 'common.eslint'; + +export default config; diff --git a/packages/features.lesson.add/index.ts b/packages/features.lesson.add/index.ts new file mode 100644 index 00000000..148fc8ed --- /dev/null +++ b/packages/features.lesson.add/index.ts @@ -0,0 +1 @@ +export { AddingLessonModal } from './src'; diff --git a/packages/features.lesson.add/package.json b/packages/features.lesson.add/package.json new file mode 100644 index 00000000..874c8b46 --- /dev/null +++ b/packages/features.lesson.add/package.json @@ -0,0 +1,55 @@ +{ + "name": "features.lesson.add", + "version": "0.0.0", + "type": "module", + "exports": { + ".": "./index.ts" + }, + "license": "MIT", + "scripts": { + "dev": "tsc --watch", + "lint": "eslint \"**/*.{ts,tsx}\"" + }, + "dependencies": { + "@tanstack/react-router": "1.120.11", + "@tanstack/react-query": "^5.73.3", + "sonner": "^1.4.0", + "common.services": "*", + "common.utils": "*", + "common.config": "*", + "common.api": "*", + "common.env": "*", + "common.types": "*", + "@xipkg/modal": "4.1.0", + "@xipkg/select": "2.2.5", + "@xipkg/utils": "1.8.0", + "@xipkg/form": "4.2.1", + "@xipkg/button": "3.2.0", + "@xipkg/input": "2.2.9", + "@xipkg/icons": "^2.5.4", + "@xipkg/datepicker": "2.2.0", + "@xipkg/inputmask": "2.0.12" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "common.typescript": "*", + "common.eslint": "*", + "@types/node": "^20.3.1", + "@types/react": "^19.0.2", + "@types/react-dom": "^19.0.2", + "@xipkg/eslint": "3.2.0", + "@xipkg/tailwind": "0.8.1", + "@xipkg/typescript": "latest", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0" + }, + "peerDependencies": { + "react": "19" + }, + "description": "adding lesson feature", + "author": "xi.effect" +} diff --git a/packages/features.lesson.add/src/hooks/index.ts b/packages/features.lesson.add/src/hooks/index.ts new file mode 100644 index 00000000..db7b1138 --- /dev/null +++ b/packages/features.lesson.add/src/hooks/index.ts @@ -0,0 +1,2 @@ +export { useAddingForm } from './useAddingForm'; +export { useConstants } from './useConstants'; diff --git a/packages/features.lesson.add/src/hooks/useAddingForm.ts b/packages/features.lesson.add/src/hooks/useAddingForm.ts new file mode 100644 index 00000000..4441b48e --- /dev/null +++ b/packages/features.lesson.add/src/hooks/useAddingForm.ts @@ -0,0 +1,66 @@ +import { useForm } from '@xipkg/form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { formSchema, type FormData } from '../model/formSchema'; +import { useFetchClassrooms } from 'common.services'; + +const DEFAULT_VALUES: FormData = { + title: '', + description: '', + studentId: '', + startTime: '09:00', + endTime: '10:00', + startDate: new Date(), + shouldRepeat: 'dont_repeat', +}; + +export const useAddingForm = () => { + const { data: classrooms } = useFetchClassrooms(); + + const form = useForm({ + resolver: zodResolver(formSchema), + mode: 'onSubmit', + defaultValues: DEFAULT_VALUES, + }); + + const { control, handleSubmit, setValue, formState, watch } = form; + const eventDate = watch('startDate'); + + const onSubmit = (data: FormData) => { + const student = classrooms?.find((c) => c.id === Number(data.studentId)); + + const student_ids = student?.kind === 'individual' ? [student.student_id] : []; + + const payload = { + title: data.title, + description: data.description || '', + student_ids, + startTime: data.startTime, + endTime: data.endTime, + startDate: data.startDate, + shouldRepeat: data.shouldRepeat, + }; + + console.log('errors', formState.errors); + + console.log('payload', payload); + }; + + const handleClearForm = () => { + setValue('title', DEFAULT_VALUES.title); + setValue('description', DEFAULT_VALUES.description); + setValue('studentId', DEFAULT_VALUES.studentId); + setValue('startTime', DEFAULT_VALUES.startTime); + setValue('endTime', DEFAULT_VALUES.endTime); + setValue('startDate', DEFAULT_VALUES.startDate); + setValue('shouldRepeat', DEFAULT_VALUES.shouldRepeat); + }; + + return { + form, + control, + eventDate, + handleSubmit, + onSubmit, + handleClearForm, + }; +}; diff --git a/packages/features.lesson.add/src/hooks/useConstants.ts b/packages/features.lesson.add/src/hooks/useConstants.ts new file mode 100644 index 00000000..bfe065c9 --- /dev/null +++ b/packages/features.lesson.add/src/hooks/useConstants.ts @@ -0,0 +1,24 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +type RepeatVariant = { + value: string; + label: string; +}; + +export const useConstants = () => { + const { t } = useTranslation('calendar'); + + const repeatVariants: RepeatVariant[] = useMemo(() => { + return [ + { value: 'dont_repeat', label: `${t('repeat_settings.dont_repeat')}` }, + { value: 'every_day', label: `${t('repeat_settings.every_day')}` }, + { value: 'every_work_day', label: `${t('repeat_settings.every_work_day')}` }, + { value: 'every_week', label: `${t('repeat_settings.every_week')}` }, + { value: 'every_2_weeks', label: `${t('repeat_settings.every_2_weeks')}` }, + { value: 'every_month', label: `${t('repeat_settings.every_month')}` }, + ]; + }, [t]); + + return { repeatVariants }; +}; diff --git a/packages/features.lesson.add/src/index.ts b/packages/features.lesson.add/src/index.ts new file mode 100644 index 00000000..276519d6 --- /dev/null +++ b/packages/features.lesson.add/src/index.ts @@ -0,0 +1,3 @@ +export { AddingLessonModal } from './ui/AddingLessonModal'; +export * from './hooks'; +export * from './model'; diff --git a/packages/features.lesson.add/src/model/formSchema.ts b/packages/features.lesson.add/src/model/formSchema.ts new file mode 100644 index 00000000..31fe6878 --- /dev/null +++ b/packages/features.lesson.add/src/model/formSchema.ts @@ -0,0 +1,48 @@ +import * as z from 'zod'; + +const timeToMinutes = (time: string): number => { + const [hours, minutes] = time.split(':').map(Number); + return hours * 60 + minutes; +}; + +// Валидация времени +const timeValidation = z.string().refine((time) => { + const timeRegex = /^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$/; + return timeRegex.test(time); +}, 'Неверный формат времени'); + +export const formSchema = z + .object({ + title: z.string(), + description: z.string().optional(), + studentId: z.string().min(1, 'Выберите студента'), + startTime: timeValidation, + endTime: timeValidation, + startDate: z.date({ required_error: 'Укажите дату' }), + shouldRepeat: z + .enum([ + 'dont_repeat', + 'every_day', + 'every_work_day', + 'every_week', + 'every_2_weeks', + 'every_month', + ]) + .default('dont_repeat'), + }) + .refine( + (data) => { + if (data.startTime && data.endTime) { + const startMinutes = timeToMinutes(data.startTime); + const endMinutes = timeToMinutes(data.endTime); + return startMinutes <= endMinutes; + } + return true; + }, + { + message: 'Время начала не может быть позже времени окончания', + path: ['startTime'], + }, + ); + +export type FormData = z.infer; diff --git a/packages/features.lesson.add/src/model/index.ts b/packages/features.lesson.add/src/model/index.ts new file mode 100644 index 00000000..5d5fd593 --- /dev/null +++ b/packages/features.lesson.add/src/model/index.ts @@ -0,0 +1 @@ +export { formSchema, type FormData } from './formSchema'; diff --git a/packages/features.lesson.add/src/ui/AddingLessonModal.tsx b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx new file mode 100644 index 00000000..7a12e9da --- /dev/null +++ b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx @@ -0,0 +1,48 @@ +import { useState } from 'react'; +import { Modal, ModalContent, ModalFooter, ModalCloseButton } from '@xipkg/modal'; +import { Button } from '@xipkg/button'; +import { Close } from '@xipkg/icons'; + +import { AddingForm } from './components/AddingForm'; +import { ModalContentWrapper } from './components/ModalContentWrapper'; + +import './AddingModal.css'; + +type AddingLessonModalProps = { + open: boolean; + onOpenChange: (open: boolean) => void; +}; + +export const AddingLessonModal = ({ open, onOpenChange }: AddingLessonModalProps) => { + const [eventDate, setEventDate] = useState(new Date()); + const handleCloseModal = () => { + onOpenChange(false); + }; + + return ( + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/features.lesson.add/src/ui/AddingModal.css b/packages/features.lesson.add/src/ui/AddingModal.css new file mode 100644 index 00000000..e15b533f --- /dev/null +++ b/packages/features.lesson.add/src/ui/AddingModal.css @@ -0,0 +1,13 @@ +/* Делаем календарь в DatePicker кликабельным */ +[data-radix-popover-content], +[data-radix-popper-content-wrapper] { + pointer-events: auto !important; +} + +/* Стили для всех элементов календаря */ +[data-radix-calendar], +[data-radix-calendar-cell], +[data-radix-calendar-day], +[data-radix-calendar-cell] button { + pointer-events: auto !important; +} \ No newline at end of file diff --git a/packages/features.lesson.add/src/ui/components/AddingForm.tsx b/packages/features.lesson.add/src/ui/components/AddingForm.tsx new file mode 100644 index 00000000..5dc8778d --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/AddingForm.tsx @@ -0,0 +1,160 @@ +import { useEffect } from 'react'; +import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@xipkg/form'; +import { Input } from '@xipkg/input'; +import { useMaskInput } from '@xipkg/inputmask'; +import { ArrowRight, Clock } from '@xipkg/icons'; +import { useAddingForm } from '../../hooks'; +import { InputDate } from './InputDate'; +import { RepeatBlock } from './RepeatBlock'; +import { StudentSelector } from './StudentSelector'; + +import type { FC, PropsWithChildren } from 'react'; +import type { FormData } from '../../model'; + +interface AddingFormProps extends PropsWithChildren { + onClose: () => void; + onDateChange: (date: Date) => void; +} + +export const AddingForm: FC = ({ children, onClose, onDateChange }) => { + const { form, control, handleSubmit, handleClearForm, onSubmit, eventDate } = useAddingForm(); + + const maskRefStartTime = useMaskInput('time'); + const maskRefEndTime = useMaskInput('time'); + + const handleReset = () => { + handleClearForm(); + onClose(); + }; + + const onFormSubmit = (data: FormData) => { + onSubmit(data); + onClose(); + }; + + useEffect(() => { + if (eventDate) { + onDateChange(eventDate); + } + }, [eventDate, onDateChange]); + + return ( +
+ +
+ ( + + Название + + + + + + )} + /> + ( + + Описание + + + + + + )} + /> +
+
+ ( + + Ученик или группа + + + + + + )} + /> +
+
+
Время
+
+ ( + + + } + variant="s" + /> + + + + )} + /> + ( + + + } + variant="s" + /> + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + )} + /> +
+
+ {children} +
+ + ); +}; diff --git a/packages/features.lesson.add/src/ui/components/DayCalendar.tsx b/packages/features.lesson.add/src/ui/components/DayCalendar.tsx new file mode 100644 index 00000000..6bfd4001 --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/DayCalendar.tsx @@ -0,0 +1,152 @@ +import { format } from 'date-fns'; +import { cn } from '@xipkg/utils'; + +import { ScrollArea } from '@xipkg/scrollarea'; +import { getFullDateString } from '../../utils/utils'; + +// Функция для создания даты с сегодняшним днем и фиксированным временем +const createTodayWithTime = (timeString: string) => { + const today = new Date(); + const [hours, minutes] = timeString.split(':').map(Number); + today.setHours(hours, minutes, 0, 0); + return today; +}; + +// Тип для события календаря +interface CalendarEventType { + id: string; + title: string; + start: Date; + end: Date; + type: string; + isAllDay: boolean; + isCancelled?: boolean; +} + +const CalendarEvent = ({ event }: { event: CalendarEventType }) => ( +
+ +); + +const MOCK_EVENTS: CalendarEventType[] = [ + { + id: '1', + title: 'Дмитрий', + start: createTodayWithTime('17:00'), + end: createTodayWithTime('18:00'), + type: 'lesson', + isAllDay: false, + }, + { + id: '2', + title: 'Отдых', + start: new Date(), + end: new Date(), + type: 'rest', + isAllDay: true, + }, + { + id: '3', + title: 'Анна', + start: createTodayWithTime('17:00'), + end: createTodayWithTime('18:00'), + type: 'lesson', + isCancelled: true, + isAllDay: false, + }, + { + id: '5', + title: 'Елена', + start: createTodayWithTime('10:00'), + end: createTodayWithTime('12:00'), + type: 'lesson', + isAllDay: false, + }, +]; + +const hours = Array.from({ length: 24 }, (_, i) => `${String(i).padStart(2, '0')}:00`); + +/** + * Адаптивный компонент календаря «День». + * ─ Первый столбец (метки времени) фиксирован шириной 5 rem. + * ─ На day-view: time-col + 1 день. + * ─ sticky-хедер с названием дня и датой, основная сетка прокручивается по вертикали. + */ +export const DayCalendar = ({ day }: { day: Date }) => { + // Шаблон колонок для CSS grid + const colTemplate = '[grid-template-columns:theme(width.20)_1fr]'; + + return ( +
+ {/* Хедер */} +
+ {getFullDateString(day, 'long')} +
+ + {/* Основная прокручиваемая зона */} + +
+ {/* Колонка времени */} +
+ {/* Весь день */} +
+ Весь день +
+ {hours.map((hour, i) => ( +
+ + {i !== 0 && hour} + +
+ ))} +
+ +
+ {/* Секция "Весь день" */} +
+ {MOCK_EVENTS.map( + (event) => event.isAllDay && , + )} +
+ + {/* Слоты часов */} + {hours.map((hour) => ( +
+ {MOCK_EVENTS.map((event) => { + const hourAsNumber = +hour.split(':')[0]; + + return ( + !event.isAllDay && + event.start.getHours() === hourAsNumber && ( + + ) + ); + })} +
+ ))} +
+
+
+
+ ); +}; diff --git a/packages/features.lesson.add/src/ui/components/InputDate.tsx b/packages/features.lesson.add/src/ui/components/InputDate.tsx new file mode 100644 index 00000000..067252d5 --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/InputDate.tsx @@ -0,0 +1,43 @@ +import { memo, useCallback, useEffect, useState } from 'react'; + +import { DatePicker } from '@xipkg/datepicker'; +import { Calendar } from '@xipkg/icons'; +import { Input } from '@xipkg/input'; +import { getFullDateString } from '../../utils/utils'; + +interface InputDateProps { + value?: Date; + onChange: (val: Date) => void; +} + +export const InputDate = memo(({ value, onChange }) => { + const [date, setDate] = useState(value || new Date()); + + const handleSelectDate = useCallback( + (newDate: Date) => { + setDate(newDate); + onChange(newDate); + }, + [onChange], + ); + + useEffect(() => { + if (value) { + setDate(value); + } + }, [value]); + + return ( + + } + /> + + ); +}); diff --git a/packages/features.lesson.add/src/ui/components/ModalContentWrapper.tsx b/packages/features.lesson.add/src/ui/components/ModalContentWrapper.tsx new file mode 100644 index 00000000..48dd8c8c --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/ModalContentWrapper.tsx @@ -0,0 +1,54 @@ +import { ModalTitle } from '@xipkg/modal'; +import { cn } from '@xipkg/utils'; +import { ScrollArea } from '@xipkg/scrollarea'; + +import { DayCalendar } from './DayCalendar'; +import type { FC, PropsWithChildren } from 'react'; + +interface ModalContentWrapperProps extends PropsWithChildren { + className?: string; + currentDay: Date; +} + +const ModalContentDesktop: FC = ({ children, currentDay, className }) => { + return ( +
+
+ +
+
+ Назначение занятия + {children} +
+
+ ); +}; + +const ModalContentMobile: FC = ({ children, currentDay, className }) => { + return ( +
+ Назначение занятия +
+
+ +
+
+ {children} +
+
+
+ ); +}; + +export const ModalContentWrapper: FC = ({ children, currentDay }) => { + return ( + <> + + + + ); +}; diff --git a/packages/features.lesson.add/src/ui/components/RepeatBlock.tsx b/packages/features.lesson.add/src/ui/components/RepeatBlock.tsx new file mode 100644 index 00000000..76d69378 --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/RepeatBlock.tsx @@ -0,0 +1,52 @@ +import { useTranslation } from 'react-i18next'; + +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectGroup, + SelectItem, + SelectSeparator, +} from '@xipkg/select'; +import { Redo } from '@xipkg/icons'; +import { useConstants } from '../../hooks'; + +import type { FC } from 'react'; + +interface RepeatBlockProps { + value: string; + onChange: (value: string) => void; +} + +export const RepeatBlock: FC = ({ value, onChange }) => { + const { t } = useTranslation('calendar'); + const { repeatVariants } = useConstants(); + + return ( + + ); +}; diff --git a/packages/features.lesson.add/src/ui/components/StudentSelector.tsx b/packages/features.lesson.add/src/ui/components/StudentSelector.tsx new file mode 100644 index 00000000..d4bbd05e --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/StudentSelector.tsx @@ -0,0 +1,30 @@ +import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from '@xipkg/select'; +import { useFetchClassrooms } from 'common.services'; + +type StudentSelectorProps = { + value: string; + onChange: (value: string) => void; +}; + +export const StudentSelector = ({ value, onChange }: StudentSelectorProps) => { + const { data: classrooms, isLoading } = useFetchClassrooms(); + + return ( + + ); +}; diff --git a/packages/features.lesson.add/src/utils/utils.ts b/packages/features.lesson.add/src/utils/utils.ts new file mode 100644 index 00000000..75b17210 --- /dev/null +++ b/packages/features.lesson.add/src/utils/utils.ts @@ -0,0 +1,6 @@ +export const getFullDateString = (date: Date, format: 'short' | 'long' = 'short') => { + const weekDayName = date.toLocaleDateString('ru-RU', { weekday: format }); + const monthName = date.toLocaleDateString('ru-RU', { month: 'long' }); + + return `${weekDayName} ${date.getDate()} ${monthName}`; +}; diff --git a/packages/features.lesson.add/tsconfig.json b/packages/features.lesson.add/tsconfig.json new file mode 100644 index 00000000..7bc23fd3 --- /dev/null +++ b/packages/features.lesson.add/tsconfig.json @@ -0,0 +1,7 @@ +{ + "include": ["src/**/*"], + "extends": [ + "common.typescript/tsconfig.app.json", + ], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/packages/pages.main/package.json b/packages/pages.main/package.json index 2bab4aa1..e0f33543 100644 --- a/packages/pages.main/package.json +++ b/packages/pages.main/package.json @@ -30,6 +30,7 @@ "common.ui": "*", "features.invites": "*", "features.invoice": "*", + "features.lesson.add": "*", "features.materials.add": "*", "features.group.add": "*", "features.materials.duplicate": "*", diff --git a/packages/pages.main/src/ui/MainPage.tsx b/packages/pages.main/src/ui/MainPage.tsx index 5510e9ce..5e236be3 100644 --- a/packages/pages.main/src/ui/MainPage.tsx +++ b/packages/pages.main/src/ui/MainPage.tsx @@ -1,14 +1,21 @@ /* eslint-disable no-irregular-whitespace */ +import { useState } from 'react'; import { ScrollArea } from '@xipkg/scrollarea'; import { Materials, Payments, Classrooms } from './components'; import { Menu } from 'common.ui'; import { useCurrentUser } from 'common.services'; // import { Sidebar } from './components/Sidebar'; -// import { AssignLessonButton } from './components/AssignLessonButton'; +import { AssignLessonButton } from './components/AssignLessonButton'; +import { AddingLessonModal } from 'features.lesson.add'; export const MainPage = () => { const { data: user } = useCurrentUser(); const isTutor = user?.default_layout === 'tutor'; + const [isOpen, setIsOpen] = useState(false); + + const handleOpenAddingModal = () => { + setIsOpen(true); + }; const steps = [ { @@ -76,20 +83,26 @@ export const MainPage = () => { ]; return ( -
- {/* */} - - {/* */} - - - {isTutor && } - - - {/* */} -
+ <> +
+ + + {/* */} + + + {isTutor && } + + + {/* */} +
+ + ); }; diff --git a/packages/pages.main/src/ui/components/AssignLessonButton/AssignLessonButton.tsx b/packages/pages.main/src/ui/components/AssignLessonButton/AssignLessonButton.tsx index 1abf1c5e..631e259d 100644 --- a/packages/pages.main/src/ui/components/AssignLessonButton/AssignLessonButton.tsx +++ b/packages/pages.main/src/ui/components/AssignLessonButton/AssignLessonButton.tsx @@ -2,8 +2,14 @@ import { useCurrentUser } from 'common.services'; import { Button } from '@xipkg/button'; import { Plus } from '@xipkg/icons'; import { cn } from '@xipkg/utils'; +import type { FC } from 'react'; -export const AssignLessonButton = ({ className }: { className?: string }) => { +type AssignLessonButtonProps = { + className?: string; + onButtonClick?: () => void; +}; + +export const AssignLessonButton: FC = ({ className, onButtonClick }) => { const { data: user } = useCurrentUser(); const isTutor = user?.default_layout === 'tutor'; @@ -12,12 +18,17 @@ export const AssignLessonButton = ({ className }: { className?: string }) => { <> {isTutor && (
- diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9f5dbbb..2b7a511f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1515,6 +1515,112 @@ importers: specifier: ^8.22.0 version: 8.46.3(eslint@9.39.1)(typescript@5.7.3) + packages/features.lesson.add: + dependencies: + '@tanstack/react-query': + specifier: ^5.73.3 + version: 5.73.3(react@19.2.0) + '@tanstack/react-router': + specifier: 1.120.11 + version: 1.120.11(react-dom@19.2.0)(react@19.2.0) + '@xipkg/button': + specifier: 3.2.0 + version: 3.2.0(@types/react@19.2.2)(react@19.2.0) + '@xipkg/datepicker': + specifier: 2.2.0 + version: 2.2.0(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) + '@xipkg/form': + specifier: 4.2.1 + version: 4.2.1(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) + '@xipkg/icons': + specifier: ^2.5.4 + version: 2.7.0(react@19.2.0) + '@xipkg/input': + specifier: 2.2.9 + version: 2.2.9(react@19.2.0) + '@xipkg/inputmask': + specifier: 2.0.12 + version: 2.0.12(react-dom@19.2.0)(react@19.2.0) + '@xipkg/modal': + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) + '@xipkg/select': + specifier: 2.2.5 + version: 2.2.5(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) + '@xipkg/utils': + specifier: 1.8.0 + version: 1.8.0(react@19.2.0) + common.api: + specifier: '*' + version: link:../common.api + common.config: + specifier: '*' + version: link:../common.config + common.env: + specifier: '*' + version: link:../common.env + common.services: + specifier: '*' + version: link:../common.services + common.types: + specifier: '*' + version: link:../common.types + common.utils: + specifier: '*' + version: link:../common.utils + react: + specifier: '19' + version: 19.2.0 + sonner: + specifier: ^1.4.0 + version: 1.7.4(react-dom@19.2.0)(react@19.2.0) + devDependencies: + '@eslint/js': + specifier: ^9.19.0 + version: 9.39.1 + '@types/node': + specifier: ^20.3.1 + version: 20.19.24 + '@types/react': + specifier: ^19.0.2 + version: 19.2.2 + '@types/react-dom': + specifier: ^19.0.2 + version: 19.2.2(@types/react@19.2.2) + '@xipkg/eslint': + specifier: 3.2.0 + version: 3.2.0(eslint-plugin-jsx-a11y@6.10.2)(eslint@9.39.1)(turbo@2.6.0)(typescript@5.7.3) + '@xipkg/tailwind': + specifier: 0.8.1 + version: 0.8.1 + '@xipkg/typescript': + specifier: latest + version: 0.2.0 + common.eslint: + specifier: '*' + version: link:../common.eslint + common.typescript: + specifier: '*' + version: link:../common.typescript + eslint: + specifier: ^9.19.0 + version: 9.39.1 + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.2.0(eslint@9.39.1) + eslint-plugin-react-refresh: + specifier: ^0.4.18 + version: 0.4.18(eslint@9.39.1) + globals: + specifier: ^15.14.0 + version: 15.15.0 + typescript: + specifier: ~5.7.2 + version: 5.7.3 + typescript-eslint: + specifier: ^8.22.0 + version: 8.46.3(eslint@9.39.1)(typescript@5.7.3) + packages/features.materials.add: dependencies: '@xipkg/button': @@ -3493,6 +3599,9 @@ importers: features.invoice: specifier: '*' version: link:../features.invoice + features.lesson.add: + specifier: '*' + version: link:../features.lesson.add features.materials.add: specifier: '*' version: link:../features.materials.add @@ -10605,7 +10714,7 @@ packages: eslint-config-airbnb: 19.0.4(eslint-plugin-import@2.31.0)(eslint-plugin-jsx-a11y@6.10.2)(eslint-plugin-react-hooks@5.1.0)(eslint-plugin-react@7.37.2)(eslint@9.39.1) eslint-config-next: 15.1.2(eslint@9.39.1)(typescript@5.7.3) eslint-config-prettier: 9.1.0(eslint@9.39.1) - eslint-config-turbo: 2.6.0(eslint@9.39.1)(turbo@2.6.0) + eslint-config-turbo: 2.6.1(eslint@9.39.1)(turbo@2.6.0) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.46.3)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1) eslint-plugin-react: 7.37.2(eslint@9.39.1) eslint-plugin-react-hooks: 5.1.0(eslint@9.39.1) @@ -10710,7 +10819,7 @@ packages: peerDependencies: react: ^19 dependencies: - '@radix-ui/react-label': 2.1.2(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) '@xipkg/utils': 1.8.0(react@19.2.0) class-variance-authority: 0.7.1 react: 19.2.0 @@ -10730,6 +10839,22 @@ packages: react: 19.2.0 dev: false + /@xipkg/modal@4.1.0(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0): + resolution: {integrity: sha512-YVuwzTqFaTgouBd7EldpUYPpd3W3hE78LISDjFv9+f9w0rqRpMFhVKQpHpo8WdVb1ahwKDiH14ZssxzkNzKBUA==} + peerDependencies: + react: ^19 + dependencies: + '@radix-ui/react-dialog': 1.1.6(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0) + '@xipkg/icons': 2.7.0(react@19.2.0) + '@xipkg/utils': 1.8.0(react@19.2.0) + lucide-react: 0.475.0(react@19.2.0) + react: 19.2.0 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - react-dom + dev: false + /@xipkg/modal@4.2.0(@types/react-dom@19.2.2)(@types/react@19.2.2)(react-dom@19.2.0)(react@19.2.0): resolution: {integrity: sha512-5uax+K526AJCkLP51+8XfFIefWVhPK0tG73Ji6gaYxrVt6zAiplReMgYkzXBwxXMMgBClYSw4+4juWB1UiGQ4g==} peerDependencies: @@ -12143,14 +12268,14 @@ packages: eslint: 9.39.1 dev: true - /eslint-config-turbo@2.6.0(eslint@9.39.1)(turbo@2.6.0): - resolution: {integrity: sha512-WdNSKL1vTl5IGyovn8w3xkaEKsK2n+l9Ybk+4oISptcom/vz9sO/9QcjEKuZSXG0zxrj9Oyx/UZA04hRxmOe+Q==} + /eslint-config-turbo@2.6.1(eslint@9.39.1)(turbo@2.6.0): + resolution: {integrity: sha512-iLTBigkgIANO89UFz/S4hGl0Ay+x/zO0lJVV9fo2F0r7n8USJxGtpyJ8QWPVBTubcanFRHbjk+e5mWatpvV0fw==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' dependencies: eslint: 9.39.1 - eslint-plugin-turbo: 2.6.0(eslint@9.39.1)(turbo@2.6.0) + eslint-plugin-turbo: 2.6.1(eslint@9.39.1)(turbo@2.6.0) turbo: 2.6.0 dev: true @@ -12377,6 +12502,17 @@ packages: turbo: 2.6.0 dev: true + /eslint-plugin-turbo@2.6.1(eslint@9.39.1)(turbo@2.6.0): + resolution: {integrity: sha512-nxwJD6ReZTxEmq/ZNJXI/Mfa3aXctTpQpJrQZESDC1bxjXXcz4i040P7wG6RsRRsQ+9eYB9D+KdCqERpwNZGzw==} + peerDependencies: + eslint: '>6.6.0' + turbo: '>2.0.0' + dependencies: + dotenv: 16.0.3 + eslint: 9.39.1 + turbo: 2.6.0 + dev: true + /eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -14936,6 +15072,16 @@ packages: seroval-plugins: 1.3.3(seroval@1.3.2) dev: true + /sonner@1.7.4(react-dom@19.2.0)(react@19.2.0): + resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + dev: false + /sonner@2.0.7(react-dom@19.2.0)(react@19.2.0): resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} peerDependencies: