From 52ac8f2a439764c81d26b297ff29b02493439709 Mon Sep 17 00:00:00 2001 From: marina-bul Date: Thu, 9 Oct 2025 07:49:32 +0300 Subject: [PATCH 01/15] feat(19): Init AddLesson form --- packages/features.lesson.add/README.md | 30 +++++ packages/features.lesson.add/eslint.config.js | 3 + packages/features.lesson.add/index.ts | 1 + packages/features.lesson.add/package.json | 53 ++++++++ .../features.lesson.add/src/hooks/index.ts | 2 + .../src/hooks/useInvoiceForm.ts | 64 ++++++++++ packages/features.lesson.add/src/index.ts | 3 + .../src/model/formSchema.ts | 49 ++++++++ .../features.lesson.add/src/model/index.ts | 1 + .../src/types/InvoiceTypes.ts | 25 ++++ .../src/ui/AddingLessonModal.tsx | 115 ++++++++++++++++++ .../src/ui/StudentSelector.tsx | 45 +++++++ packages/features.lesson.add/tsconfig.json | 7 ++ pnpm-lock.yaml | 114 +++++++++++++++-- 14 files changed, 505 insertions(+), 7 deletions(-) create mode 100644 packages/features.lesson.add/README.md create mode 100644 packages/features.lesson.add/eslint.config.js create mode 100644 packages/features.lesson.add/index.ts create mode 100644 packages/features.lesson.add/package.json create mode 100644 packages/features.lesson.add/src/hooks/index.ts create mode 100644 packages/features.lesson.add/src/hooks/useInvoiceForm.ts create mode 100644 packages/features.lesson.add/src/index.ts create mode 100644 packages/features.lesson.add/src/model/formSchema.ts create mode 100644 packages/features.lesson.add/src/model/index.ts create mode 100644 packages/features.lesson.add/src/types/InvoiceTypes.ts create mode 100644 packages/features.lesson.add/src/ui/AddingLessonModal.tsx create mode 100644 packages/features.lesson.add/src/ui/StudentSelector.tsx create mode 100644 packages/features.lesson.add/tsconfig.json 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..bfec3d57 --- /dev/null +++ b/packages/features.lesson.add/package.json @@ -0,0 +1,53 @@ +{ + "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" + }, + "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..6b103bfa --- /dev/null +++ b/packages/features.lesson.add/src/hooks/index.ts @@ -0,0 +1,2 @@ +export { useInvoiceForm } from './useInvoiceForm'; +export { useCreateInvoice } from './useCreateInvoice'; diff --git a/packages/features.lesson.add/src/hooks/useInvoiceForm.ts b/packages/features.lesson.add/src/hooks/useInvoiceForm.ts new file mode 100644 index 00000000..d93c86ca --- /dev/null +++ b/packages/features.lesson.add/src/hooks/useInvoiceForm.ts @@ -0,0 +1,64 @@ +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().toLocaleDateString('ru-RU'), + shouldRepeat: 'dont_repeat', +}; + +export const useInvoiceForm = () => { + const { data: classrooms } = useFetchClassrooms(); + + const form = useForm({ + resolver: zodResolver(formSchema), + mode: 'onSubmit', + defaultValues: DEFAULT_VALUES, + }); + + const { control, handleSubmit, setValue, formState } = form; + + console.log('errors', formState.errors); + + 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('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, + handleSubmit, + onSubmit, + handleClearForm, + }; +}; 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..da7baa12 --- /dev/null +++ b/packages/features.lesson.add/src/model/formSchema.ts @@ -0,0 +1,49 @@ +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 + .string() + .min(1, 'Укажите дату') + .regex(/^\d{2}\.\d{2}\.\d{4}$/, 'Формат даты: дд.мм.гггг'), + 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/types/InvoiceTypes.ts b/packages/features.lesson.add/src/types/InvoiceTypes.ts new file mode 100644 index 00000000..7bb50701 --- /dev/null +++ b/packages/features.lesson.add/src/types/InvoiceTypes.ts @@ -0,0 +1,25 @@ +export type StudentT = { + id: string; + name: string; + subjects: SubjectT[]; +}; + +export type SubjectT = { + id: string; + name: string; + variant: string; + pricePerLesson: number; + unpaidLessonsAmount?: number; +}; + +export interface CreateInvoicePayload { + invoice: { + comment: string; + }; + items: Array<{ + name: string; + price: number; + quantity: number; + }>; + student_ids: number[]; +} 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..55657440 --- /dev/null +++ b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx @@ -0,0 +1,115 @@ +import { + Modal, + ModalContent, + ModalHeader, + ModalTitle, + ModalFooter, + ModalCloseButton, +} from '@xipkg/modal'; +import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@xipkg/form'; +import { Button } from '@xipkg/button'; +import { Close } from '@xipkg/icons'; +import { Input } from '@xipkg/input'; +import { useInvoiceForm } from '../hooks'; +import type { FormData } from '../model'; +import { StudentSelector } from './StudentSelector'; + +type AddingLessonModalProps = { + open: boolean; + onOpenChange: (open: boolean) => void; +}; + +export const AddingLessonModal = ({ open, onOpenChange }: AddingLessonModalProps) => { + const { + form, + control, + handleSubmit, + handleClearForm, + onSubmit, + } = useInvoiceForm(); + + + const handleCloseModal = () => { + handleClearForm(); + onOpenChange(false); + }; + + const onFormSubmit = (data: FormData) => { + onSubmit(data); + handleCloseModal(); + }; + + + return ( + + + + + +
+
+
Вторник, 23 сентября
+

Здесь будет календарь на день

+
+
+ + Назначение занятия + +
+ + ( + + Название + + + + + + )} + /> + ( + + Описание + + + + + + )} + /> + + + + + + + + + +
+ +
+ +
+
+ ); +}; diff --git a/packages/features.lesson.add/src/ui/StudentSelector.tsx b/packages/features.lesson.add/src/ui/StudentSelector.tsx new file mode 100644 index 00000000..3aeea98e --- /dev/null +++ b/packages/features.lesson.add/src/ui/StudentSelector.tsx @@ -0,0 +1,45 @@ +import { FormControl, FormField, FormItem, FormLabel } from '@xipkg/form'; +import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from '@xipkg/select'; +import { useFetchClassrooms } from 'common.services'; + +type StudentSelectorProps = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + control: any; +}; + +export const StudentSelector = ({ control }: StudentSelectorProps) => { + const { data: classrooms, isLoading } = useFetchClassrooms(); + + return ( + ( + + Ученик или группа + + + + + )} + /> + ); +}; 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/pnpm-lock.yaml b/pnpm-lock.yaml index d1dc4fcd..e17c391d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1157,6 +1157,106 @@ importers: specifier: ^8.22.0 version: 8.44.0(eslint@9.36.0)(typescript@5.7.3) + packages/features.lesson.add: + dependencies: + '@tanstack/react-query': + specifier: ^5.73.3 + version: 5.73.3(react@19.1.1) + '@tanstack/react-router': + specifier: 1.120.11 + version: 1.120.11(react-dom@19.1.1)(react@19.1.1) + '@xipkg/button': + specifier: 3.2.0 + version: 3.2.0(@types/react@19.1.13)(react@19.1.1) + '@xipkg/form': + specifier: 4.2.1 + version: 4.2.1(@types/react-dom@19.1.9)(@types/react@19.1.13)(react-dom@19.1.1)(react@19.1.1) + '@xipkg/icons': + specifier: ^2.5.4 + version: 2.5.4(react@19.1.1) + '@xipkg/input': + specifier: 2.2.9 + version: 2.2.9(react@19.1.1) + '@xipkg/modal': + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.1.9)(@types/react@19.1.13)(react-dom@19.1.1)(react@19.1.1) + '@xipkg/select': + specifier: 2.2.5 + version: 2.2.5(@types/react-dom@19.1.9)(@types/react@19.1.13)(react-dom@19.1.1)(react@19.1.1) + '@xipkg/utils': + specifier: 1.8.0 + version: 1.8.0(react@19.1.1) + 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.1.1 + sonner: + specifier: ^1.4.0 + version: 1.7.4(react-dom@19.1.1)(react@19.1.1) + devDependencies: + '@eslint/js': + specifier: ^9.19.0 + version: 9.36.0 + '@types/node': + specifier: ^20.3.1 + version: 20.19.17 + '@types/react': + specifier: ^19.0.2 + version: 19.1.13 + '@types/react-dom': + specifier: ^19.0.2 + version: 19.1.9(@types/react@19.1.13) + '@xipkg/eslint': + specifier: 3.2.0 + version: 3.2.0(eslint-plugin-jsx-a11y@6.10.2)(eslint@9.36.0)(turbo@2.5.6)(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.36.0 + eslint-plugin-react-hooks: + specifier: ^5.0.0 + version: 5.2.0(eslint@9.36.0) + eslint-plugin-react-refresh: + specifier: ^0.4.18 + version: 0.4.18(eslint@9.36.0) + 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.44.0(eslint@9.36.0)(typescript@5.7.3) + packages/features.materials.add: dependencies: '@xipkg/button': @@ -9497,7 +9597,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.36.0) eslint-config-next: 15.1.2(eslint@9.36.0)(typescript@5.7.3) eslint-config-prettier: 9.1.0(eslint@9.36.0) - eslint-config-turbo: 2.5.7(eslint@9.36.0)(turbo@2.5.6) + eslint-config-turbo: 2.5.8(eslint@9.36.0)(turbo@2.5.6) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.44.0)(eslint-import-resolver-typescript@3.10.1)(eslint@9.36.0) eslint-plugin-react: 7.37.2(eslint@9.36.0) eslint-plugin-react-hooks: 5.1.0(eslint@9.36.0) @@ -9602,7 +9702,7 @@ packages: peerDependencies: react: ^19 dependencies: - '@radix-ui/react-label': 2.1.2(@types/react-dom@19.1.9)(@types/react@19.1.13)(react-dom@19.1.1)(react@19.1.1) + '@radix-ui/react-label': 2.1.7(@types/react-dom@19.1.9)(@types/react@19.1.13)(react-dom@19.1.1)(react@19.1.1) '@xipkg/utils': 1.8.0(react@19.1.1) class-variance-authority: 0.7.1 react: 19.1.1 @@ -11026,14 +11126,14 @@ packages: eslint: 9.36.0 dev: true - /eslint-config-turbo@2.5.7(eslint@9.36.0)(turbo@2.5.6): - resolution: {integrity: sha512-6p1qq5I/gO8tkPhZmhO5Ug5VJPV6JX/BPaYanToYm8DwALHW3eC3HHsToeQ6Rq1xaXPFZJNAD27nZVLcr8FQkg==} + /eslint-config-turbo@2.5.8(eslint@9.36.0)(turbo@2.5.6): + resolution: {integrity: sha512-wzxmN7dJNFGDwOvR/4j8U2iaIH/ruYez8qg/sCKrezJ3+ljbFMvJLmgKKt/1mDuyU9wj5aZqO6VijP3QH169FA==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' dependencies: eslint: 9.36.0 - eslint-plugin-turbo: 2.5.7(eslint@9.36.0)(turbo@2.5.6) + eslint-plugin-turbo: 2.5.8(eslint@9.36.0)(turbo@2.5.6) turbo: 2.5.6 dev: true @@ -11260,8 +11360,8 @@ packages: turbo: 2.5.6 dev: true - /eslint-plugin-turbo@2.5.7(eslint@9.36.0)(turbo@2.5.6): - resolution: {integrity: sha512-FXTyV3Lmk5xzut5lpXr6YwYwrvoI7nDveOmSzvePZMgJfZ/zMh5rVTN1xSpL5sWe9LkI7ZYPazj/aUrLZfbZIA==} + /eslint-plugin-turbo@2.5.8(eslint@9.36.0)(turbo@2.5.6): + resolution: {integrity: sha512-bVjx4vTH0oTKIyQ7EGFAXnuhZMrKIfu17qlex/dps7eScPnGQLJ3r1/nFq80l8xA+8oYjsSirSQ2tXOKbz3kEw==} peerDependencies: eslint: '>6.6.0' turbo: '>2.0.0' From 5fc3233886753cbee7e3e2fb3550b339261e0809 Mon Sep 17 00:00:00 2001 From: marina-bul Date: Tue, 21 Oct 2025 09:48:58 +0300 Subject: [PATCH 02/15] feat(19): Add date inputs and calendar block --- apps/xi.web/src/index.css | 1 + .../features.lesson.add/src/hooks/index.ts | 3 +- .../{useInvoiceForm.ts => useAddingForm.ts} | 2 +- .../src/model/formSchema.ts | 70 +- .../src/ui/AddingLessonModal.tsx | 189 +- .../src/ui/DayCalendar.tsx | 152 + .../src/ui/StudentSelector.tsx | 8 +- packages/pages.main/package.json | 1 + packages/pages.main/src/ui/MainPage.tsx | 46 +- .../AssignLessonButton/AssignLessonButton.tsx | 15 +- pnpm-lock.yaml | 4969 ++++++++--------- 11 files changed, 2791 insertions(+), 2665 deletions(-) rename packages/features.lesson.add/src/hooks/{useInvoiceForm.ts => useAddingForm.ts} (97%) create mode 100644 packages/features.lesson.add/src/ui/DayCalendar.tsx diff --git a/apps/xi.web/src/index.css b/apps/xi.web/src/index.css index 40a4a9de..5695395a 100644 --- a/apps/xi.web/src/index.css +++ b/apps/xi.web/src/index.css @@ -32,6 +32,7 @@ @source "../../../packages/pages.invites/src"; @source "../../../packages/pages.reset-password/src"; @source "../../../packages/features.invoice/src"; +@source "../../../packages/features.lesson.add/src"; @source "../../../node_modules/@xipkg"; diff --git a/packages/features.lesson.add/src/hooks/index.ts b/packages/features.lesson.add/src/hooks/index.ts index 6b103bfa..be717e99 100644 --- a/packages/features.lesson.add/src/hooks/index.ts +++ b/packages/features.lesson.add/src/hooks/index.ts @@ -1,2 +1 @@ -export { useInvoiceForm } from './useInvoiceForm'; -export { useCreateInvoice } from './useCreateInvoice'; +export { useAddingForm } from './useAddingForm'; diff --git a/packages/features.lesson.add/src/hooks/useInvoiceForm.ts b/packages/features.lesson.add/src/hooks/useAddingForm.ts similarity index 97% rename from packages/features.lesson.add/src/hooks/useInvoiceForm.ts rename to packages/features.lesson.add/src/hooks/useAddingForm.ts index d93c86ca..fb25442d 100644 --- a/packages/features.lesson.add/src/hooks/useInvoiceForm.ts +++ b/packages/features.lesson.add/src/hooks/useAddingForm.ts @@ -13,7 +13,7 @@ const DEFAULT_VALUES: FormData = { shouldRepeat: 'dont_repeat', }; -export const useInvoiceForm = () => { +export const useAddingForm = () => { const { data: classrooms } = useFetchClassrooms(); const form = useForm({ diff --git a/packages/features.lesson.add/src/model/formSchema.ts b/packages/features.lesson.add/src/model/formSchema.ts index da7baa12..91329c24 100644 --- a/packages/features.lesson.add/src/model/formSchema.ts +++ b/packages/features.lesson.add/src/model/formSchema.ts @@ -11,39 +11,41 @@ const timeValidation = z.string().refine((time) => { 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 - .string() - .min(1, 'Укажите дату') - .regex(/^\d{2}\.\d{2}\.\d{4}$/, 'Формат даты: дд.мм.гггг'), - 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 const formSchema = z + .object({ + title: z.string(), + description: z.string().optional(), + studentId: z.string().min(1, 'Выберите'), + startTime: timeValidation, + endTime: timeValidation, + startDate: z + .string() + .min(1, 'Укажите дату') + .regex(/^\d{2}\.\d{2}\.\d{4}$/, 'Формат даты: дд.мм.гггг'), + 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/ui/AddingLessonModal.tsx b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx index 55657440..bb03b920 100644 --- a/packages/features.lesson.add/src/ui/AddingLessonModal.tsx +++ b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx @@ -1,18 +1,12 @@ -import { - Modal, - ModalContent, - ModalHeader, - ModalTitle, - ModalFooter, - ModalCloseButton, -} from '@xipkg/modal'; +import { Modal, ModalContent, ModalTitle, ModalFooter, ModalCloseButton } from '@xipkg/modal'; import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@xipkg/form'; import { Button } from '@xipkg/button'; import { Close } from '@xipkg/icons'; import { Input } from '@xipkg/input'; -import { useInvoiceForm } from '../hooks'; +import { useAddingForm } from '../hooks'; import type { FormData } from '../model'; import { StudentSelector } from './StudentSelector'; +import { DayCalendar } from './DayCalendar'; type AddingLessonModalProps = { open: boolean; @@ -20,14 +14,7 @@ type AddingLessonModalProps = { }; export const AddingLessonModal = ({ open, onOpenChange }: AddingLessonModalProps) => { - const { - form, - control, - handleSubmit, - handleClearForm, - onSubmit, - } = useInvoiceForm(); - + const { form, control, handleSubmit, handleClearForm, onSubmit } = useAddingForm(); const handleCloseModal = () => { handleClearForm(); @@ -39,76 +26,122 @@ export const AddingLessonModal = ({ open, onOpenChange }: AddingLessonModalProps handleCloseModal(); }; - return ( - + -
-
-
Вторник, 23 сентября
-

Здесь будет календарь на день

+
+
+
-
- - Назначение занятия - -
- - ( - - Название - - - - - - )} - /> - ( - - Описание - - - - - - )} - /> - +
+ Назначение занятия + + +
+ ( + + Название + + + + + + )} + /> + ( + + Описание + + + + + + )} + /> +
+
+ +
+
+
Время
+
+ ( + + + + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + + )} + /> +
+
- - - - - - - + + + + + +
-
- ); diff --git a/packages/features.lesson.add/src/ui/DayCalendar.tsx b/packages/features.lesson.add/src/ui/DayCalendar.tsx new file mode 100644 index 00000000..61321c08 --- /dev/null +++ b/packages/features.lesson.add/src/ui/DayCalendar.tsx @@ -0,0 +1,152 @@ +import { format } from 'date-fns'; +import { cn } from '@xipkg/utils'; + +import { ScrollArea } from '@xipkg/scrollarea'; + +// Функция для создания даты с сегодняшним днем и фиксированным временем +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 ( +
+ {/* Хедер */} +
+ {format(day, 'd')} {format(day, 'MMMM')} +
+ + {/* Основная прокручиваемая зона */} + +
+ {/* Колонка времени */} +
+ {/* Весь день */} +
+ Весь день +
+ {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/StudentSelector.tsx b/packages/features.lesson.add/src/ui/StudentSelector.tsx index 3aeea98e..1aafdc60 100644 --- a/packages/features.lesson.add/src/ui/StudentSelector.tsx +++ b/packages/features.lesson.add/src/ui/StudentSelector.tsx @@ -17,13 +17,11 @@ export const StudentSelector = ({ control }: StudentSelectorProps) => { defaultValue="" render={({ field }) => ( - Ученик или группа + Ученик или группа - - - - )} - /> - ( - - Описание - - - - - - )} - /> -
-
- -
-
-
Время
-
- ( - - - - - - - )} - /> - ( - - - - - - - )} - /> - ( - - - - - - - )} - /> - ( - - - - - - - )} - /> -
-
- - - - - - + + + + + +
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..47a2a183 --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/AddingForm.tsx @@ -0,0 +1,139 @@ +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; +} + +export const AddingForm: FC = ({ children, onClose }) => { + const { form, control, handleSubmit, handleClearForm, onSubmit } = useAddingForm(); + + const maskRefStartTime = useMaskInput('time'); + const maskRefEndTime = useMaskInput('time'); + + const handleReset = () => { + handleClearForm(); + onClose(); + }; + + const onFormSubmit = (data: FormData) => { + onSubmit(data); + onClose(); + }; + + return ( +
+ +
+ ( + + Название + + + + + + )} + /> + ( + + Описание + + + + + + )} + /> +
+
+ +
+
+
Время
+
+ ( + + + } + variant="s" + /> + + + + )} + /> + ( + + + } + variant="s" + /> + + + + )} + /> + ( + + + + + + + )} + /> + ( + + + + + + )} + /> +
+
+ {children} +
+ + ); +}; 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..6c352e0a --- /dev/null +++ b/packages/features.lesson.add/src/ui/components/InputDate.tsx @@ -0,0 +1,35 @@ +import { memo, useCallback, useEffect, useState } from 'react'; + +import { DatePicker } from '@xipkg/datepicker'; +import { Calendar } from '@xipkg/icons'; +import { Input } from '@xipkg/input'; +import { convertStringToDate, getFullDateString } from '../../utils/utils'; + +interface InputDateProps { + value?: string; +} + +export const InputDate = memo(({ value }) => { + const [date, setDate] = useState(convertStringToDate(value || '')); + + const handleSelectDate = useCallback((newDate: Date) => { + setDate(newDate); + }, []); + + useEffect(() => { + setDate(convertStringToDate(value || '')); + }, [value]); + + 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..05a56f0b --- /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/StudentSelector.tsx b/packages/features.lesson.add/src/ui/components/StudentSelector.tsx similarity index 100% rename from packages/features.lesson.add/src/ui/StudentSelector.tsx rename to packages/features.lesson.add/src/ui/components/StudentSelector.tsx 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..80760ac5 --- /dev/null +++ b/packages/features.lesson.add/src/utils/utils.ts @@ -0,0 +1,12 @@ +import { parse } from 'date-fns'; + +export const getFullDateString = (date: Date) => { + const weekDayName = date.toLocaleDateString('ru-RU', { weekday: 'short' }); + const monthName = date.toLocaleDateString('ru-RU', { month: 'long' }); + + return `${weekDayName} ${date.getDate()} ${monthName}`; +}; + +export const convertStringToDate = (dateString: string): Date => { + return parse(dateString, 'dd.MM.yyyy', new Date()); +}; From 36e5f992360f19e7fc296715866eed00f1d1fd2a Mon Sep 17 00:00:00 2001 From: marina-bul Date: Wed, 12 Nov 2025 23:53:44 +0300 Subject: [PATCH 04/15] fix(19): Fix datepicker, schema and date components --- .../src/hooks/useAddingForm.ts | 6 ++-- .../src/model/formSchema.ts | 5 +--- .../src/ui/AddingLessonModal.tsx | 10 +++++-- .../src/ui/AddingModal.css | 30 +++++++++++++++++++ .../src/ui/DayCalendar.tsx | 3 +- .../src/ui/components/AddingForm.tsx | 15 +++++++--- .../src/ui/components/InputDate.tsx | 25 +++++++++++----- .../src/ui/components/RepeatBlock.tsx | 2 +- .../features.lesson.add/src/utils/utils.ts | 4 +-- pnpm-lock.yaml | 6 ++++ 10 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 packages/features.lesson.add/src/ui/AddingModal.css diff --git a/packages/features.lesson.add/src/hooks/useAddingForm.ts b/packages/features.lesson.add/src/hooks/useAddingForm.ts index fb25442d..806a9ec6 100644 --- a/packages/features.lesson.add/src/hooks/useAddingForm.ts +++ b/packages/features.lesson.add/src/hooks/useAddingForm.ts @@ -9,7 +9,7 @@ const DEFAULT_VALUES: FormData = { studentId: '', startTime: '09:00', endTime: '10:00', - startDate: new Date().toLocaleDateString('ru-RU'), + startDate: new Date(), shouldRepeat: 'dont_repeat', }; @@ -22,7 +22,8 @@ export const useAddingForm = () => { defaultValues: DEFAULT_VALUES, }); - const { control, handleSubmit, setValue, formState } = form; + const { control, handleSubmit, setValue, formState, watch } = form; + const eventDate = watch('startDate'); console.log('errors', formState.errors); @@ -57,6 +58,7 @@ export const useAddingForm = () => { return { form, control, + eventDate, handleSubmit, onSubmit, handleClearForm, diff --git a/packages/features.lesson.add/src/model/formSchema.ts b/packages/features.lesson.add/src/model/formSchema.ts index d53639ea..31fe6878 100644 --- a/packages/features.lesson.add/src/model/formSchema.ts +++ b/packages/features.lesson.add/src/model/formSchema.ts @@ -18,10 +18,7 @@ export const formSchema = z studentId: z.string().min(1, 'Выберите студента'), startTime: timeValidation, endTime: timeValidation, - startDate: z - .string() - .min(1, 'Укажите дату') - .regex(/^\d{2}\.\d{2}\.\d{4}$/, 'Формат даты: дд.мм.гггг'), + startDate: z.date({ required_error: 'Укажите дату' }), shouldRepeat: z .enum([ 'dont_repeat', diff --git a/packages/features.lesson.add/src/ui/AddingLessonModal.tsx b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx index fdf15a7a..3b2d7e0c 100644 --- a/packages/features.lesson.add/src/ui/AddingLessonModal.tsx +++ b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { Modal, ModalContent, ModalTitle, ModalFooter, ModalCloseButton } from '@xipkg/modal'; import { Button } from '@xipkg/button'; import { Close } from '@xipkg/icons'; @@ -5,30 +6,33 @@ import { Close } from '@xipkg/icons'; import { DayCalendar } from './DayCalendar'; import { AddingForm } from './components/AddingForm'; +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/modules.navigation/src/ui/Header/Header.tsx b/packages/modules.navigation/src/ui/Header/Header.tsx index 8ee2e446..ef5be45c 100644 --- a/packages/modules.navigation/src/ui/Header/Header.tsx +++ b/packages/modules.navigation/src/ui/Header/Header.tsx @@ -74,6 +74,7 @@ export const Header = () => { /> ) : ( { <> - diff --git a/packages/modules.navigation/src/ui/Navigation.tsx b/packages/modules.navigation/src/ui/Navigation.tsx index 6fd31c93..eb19db0c 100644 --- a/packages/modules.navigation/src/ui/Navigation.tsx +++ b/packages/modules.navigation/src/ui/Navigation.tsx @@ -1,7 +1,7 @@ import { useMediaQuery } from '@xipkg/utils'; import { Drawer, DrawerContent } from '@xipkg/drawer'; import { Sidebar, SidebarInset } from '@xipkg/sidebar'; -import { Header } from './Header'; +// import { Header } from './Header'; import { SideBarItems } from './SideBarItems'; import { SidebarProvider } from '@xipkg/sidebar'; import { useMenuStore } from '../store'; @@ -37,7 +37,7 @@ const NavigationLayout = ({ children }: { children: React.ReactNode }) => { @@ -46,7 +46,7 @@ const NavigationLayout = ({ children }: { children: React.ReactNode }) => { {/* Children всегда рендерятся в одном месте с одним ключом и одним типом элемента */} {/* Используем SidebarInset для обоих случаев, чтобы React сохранял состояние */} {stableChildren} @@ -78,7 +78,7 @@ export const Navigation = ({ children }: { children: React.ReactNode }) => { open={sidebarOpen} onOpenChange={sidebarOnOpenChange} > -
+ {/*
*/} {stableChildren} ); diff --git a/packages/modules.navigation/src/ui/SideBarItems.tsx b/packages/modules.navigation/src/ui/SideBarItems.tsx index 0ca1b51e..71a7ba61 100644 --- a/packages/modules.navigation/src/ui/SideBarItems.tsx +++ b/packages/modules.navigation/src/ui/SideBarItems.tsx @@ -6,6 +6,7 @@ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, + SidebarTrigger, } from '@xipkg/sidebar'; import { useLocation, useNavigate, useParams, useSearch } from '@tanstack/react-router'; import { useTranslation } from 'react-i18next'; @@ -13,14 +14,24 @@ import { Group, Home, Payments, TelegramFilled, InfoCircle, BookOpened } from '@ import { useCurrentUser } from 'common.services'; import { useCallStore } from 'modules.calls'; import { useMenuStore } from '../store'; +import { Notifications } from './Header/Notifications'; +import { Logo, SmallLogo } from 'common.ui'; +import { useMediaQuery } from '@xipkg/utils'; +import { DesktopUserMenu } from './Header/DesktopUserMenu'; +import { useEffect, useState } from 'react'; +import { useAuth } from 'common.auth'; export const SideBarItems = () => { const { t } = useTranslation('navigation'); - const { close } = useMenuStore(); + const { close, isDesktopOpen } = useMenuStore(); + const isMobile = useMediaQuery('(max-width: 960px)'); const { data: user } = useCurrentUser(); const isTutor = user?.default_layout === 'tutor'; + // Определяем, закрыт ли сайдбар (для десктопа) + const isCollapsed = !isMobile && !isDesktopOpen; + const isStarted = useCallStore((state) => state.isStarted); const mode = useCallStore((state) => state.mode); const updateStore = useCallStore((state) => state.updateStore); @@ -136,9 +147,53 @@ export const SideBarItems = () => { close(); }; + const { logout } = useAuth(); + + const [open, setOpen] = useState(false); + + // Синхронизируем состояние модалки с URL + useEffect(() => { + const profileParam = search.profile; + + const hasProfileParam = !!profileParam; + if (hasProfileParam !== open) { + setOpen(hasProfileParam); + } + }, [search.profile, open]); + + const handleOpenProfile = () => { + navigate({ + to: pathname, + search: { profile: 'personalInfo' }, + }); + setOpen(true); + }; + + const handleLogout = () => { + logout(); + // TODO: переделать, сделать редирект только по 200 + navigate({ to: '/signin' }); + }; + return ( <> + {/* Верхняя секция: профиль, бургер-меню, колокольчик */} +
+ {/* Профиль пользователя */} + +
+ +
+
+ {/* Основное меню */} @@ -150,7 +205,7 @@ export const SideBarItems = () => { data-umami-event={`navigation-${item.titleKey}`} data-umami-event-url={item.url} > - + {t(item.titleKey)} @@ -160,8 +215,9 @@ export const SideBarItems = () => {
- + + {footerMenu.map((item) => ( { ? { 'data-umami-event-url': 'https://t.me/sovlium_support_bot' } : {})} > - +
{t(item.titleKey)}
))}
+ {/* Логотип внизу — оба в DOM для плавного перехода без мигания */} +
+
+ +
+
+ +
+
); diff --git a/packages/modules.profile/package.json b/packages/modules.profile/package.json index 952c324e..142c3b30 100644 --- a/packages/modules.profile/package.json +++ b/packages/modules.profile/package.json @@ -19,7 +19,7 @@ "@xipkg/modal": "^4.3.1", "@xipkg/select": "2.2.5", "@xipkg/toggle": "^2.0.13", - "@xipkg/userprofile": "4.0.14", + "@xipkg/userprofile": "4.1.0", "@xipkg/utils": "1.8.0", "common.api": "*", "common.auth": "*", diff --git a/packages/pages.classroom/package.json b/packages/pages.classroom/package.json index 030ab971..c6ae2feb 100644 --- a/packages/pages.classroom/package.json +++ b/packages/pages.classroom/package.json @@ -23,7 +23,7 @@ "@xipkg/select": "^2.2.5", "@xipkg/tabs": "2.1.0", "@xipkg/tooltip": "2.1.0", - "@xipkg/userprofile": "4.0.14", + "@xipkg/userprofile": "4.1.0", "common.utils": "*", "common.api": "*", "common.entities": "*", diff --git a/packages/pages.classrooms/package.json b/packages/pages.classrooms/package.json index 529dab0e..c1ab122b 100644 --- a/packages/pages.classrooms/package.json +++ b/packages/pages.classrooms/package.json @@ -23,7 +23,7 @@ "@xipkg/scrollarea": "2.2.0", "@xipkg/select": "^2.2.5", "@xipkg/tooltip": "2.1.0", - "@xipkg/userprofile": "4.0.14", + "@xipkg/userprofile": "4.1.0", "common.entities": "*", "common.env": "*", "common.services": "*", diff --git a/packages/pages.main/package.json b/packages/pages.main/package.json index e2c5b356..e23434ae 100644 --- a/packages/pages.main/package.json +++ b/packages/pages.main/package.json @@ -19,7 +19,7 @@ "@xipkg/icons": "^3.0.3", "@xipkg/scrollarea": "2.2.0", "@xipkg/tooltip": "2.1.0", - "@xipkg/userprofile": "4.0.14", + "@xipkg/userprofile": "4.1.0", "@xipkg/utils": "1.8.0", "common.api": "*", "common.auth": "*", diff --git a/packages/pages.main/src/ui/MainPage.tsx b/packages/pages.main/src/ui/MainPage.tsx index ce4cc765..57c4d5ac 100644 --- a/packages/pages.main/src/ui/MainPage.tsx +++ b/packages/pages.main/src/ui/MainPage.tsx @@ -1,6 +1,13 @@ /* eslint-disable no-irregular-whitespace */ import { ScrollArea } from '@xipkg/scrollarea'; -import { Materials, Payments, Classrooms } from './components'; +import { + Materials, + Payments, + Classrooms, + Lessons, + ActionButtons, + DateTimeDisplay, +} from './components'; import { Menu } from 'common.ui'; import { useCurrentUser } from 'common.services'; // import { Sidebar } from './components/Sidebar'; @@ -115,20 +122,36 @@ export const MainPage = () => { ]; return ( -
- {/* */} +
- {/* */} - - - {isTutor && } +
+ {/* Дата и время */} + + + {/* Кнопки действий сверху */} + + + {/* Две колонки: Расписание слева, Кабинеты справа */} +
+
+ +
+
+ +
+
+ + {/* Финансовый блок */} + + + {/* Материалы */} + {isTutor && } +
- {/* */}
); }; diff --git a/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx b/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx new file mode 100644 index 00000000..7d927fb1 --- /dev/null +++ b/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx @@ -0,0 +1,41 @@ +import { Button } from '@xipkg/button'; +import { ModalInvitation } from 'features.invites'; +import { ModalAddGroup } from 'features.group.add'; +import { useCurrentUser } from 'common.services'; + +export const ActionButtons = () => { + const { data: user } = useCurrentUser(); + const isTutor = user?.default_layout === 'tutor'; + + if (!isTutor) { + return null; + } + + return ( +
+ + + + + + + +
+ ); +}; diff --git a/packages/pages.main/src/ui/components/ActionButtons/index.ts b/packages/pages.main/src/ui/components/ActionButtons/index.ts new file mode 100644 index 00000000..99548094 --- /dev/null +++ b/packages/pages.main/src/ui/components/ActionButtons/index.ts @@ -0,0 +1 @@ +export { ActionButtons } from './ActionButtons'; diff --git a/packages/pages.main/src/ui/components/Classrooms/ClassroomsTutor.tsx b/packages/pages.main/src/ui/components/Classrooms/ClassroomsTutor.tsx index 8118dca4..107a52e9 100644 --- a/packages/pages.main/src/ui/components/Classrooms/ClassroomsTutor.tsx +++ b/packages/pages.main/src/ui/components/Classrooms/ClassroomsTutor.tsx @@ -1,89 +1,109 @@ import { Button } from '@xipkg/button'; -import { ArrowRight } from '@xipkg/icons'; -import { ScrollArea } from '@xipkg/scrollarea'; +import { Group, Search } from '@xipkg/icons'; +import { Input } from '@xipkg/input'; import { Classroom } from './Classroom'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@xipkg/tooltip'; -import { useNavigate } from '@tanstack/react-router'; -import { ModalInvitation } from 'features.invites'; import { useFetchClassrooms } from 'common.services'; -import { ModalAddGroup } from 'features.group.add'; import { useNoteVisibility } from '../../../hooks'; import { NoteForStudent } from './NoteForStudent'; +import { useState, useMemo } from 'react'; +import { cn } from '@xipkg/utils'; export const ClassroomsTutor = () => { const { data: classrooms, isLoading } = useFetchClassrooms(); const { isHidden, hideNote } = useNoteVisibility(true); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedSubject, setSelectedSubject] = useState('all'); - const navigate = useNavigate(); + // Фильтрация кабинетов по поисковому запросу и предмету + const filteredClassrooms = useMemo(() => { + if (!classrooms) return []; - const handleMore = () => { - navigate({ - to: '/classrooms', + return classrooms.filter((classroom) => { + // Фильтр по поисковому запросу (по имени кабинета или ученика) + const matchesSearch = + searchQuery === '' || classroom.name?.toLowerCase().includes(searchQuery.toLowerCase()); + + // Фильтр по предмету + const matchesSubject = + selectedSubject === 'all' || + (selectedSubject === 'english' && classroom.subject_id === 1) || // TODO: Заменить на реальные ID предметов + (selectedSubject === 'math' && classroom.subject_id === 2); + + return matchesSearch && matchesSubject; }); - }; + }, [classrooms, searchQuery, selectedSubject]); + + const subjects = [ + { id: 'all', label: 'Все предметы' }, + { id: 'english', label: 'Английский' }, + { id: 'math', label: 'Математика' }, + ]; return ( -
+

Кабинеты

- - - - - К кабинетам - +
+ +
+
-
- - - + {/* Поиск */} + } + value={searchQuery} + onChange={(e) => setSearchQuery(e.target.value)} + /> - - - -
+ {/* Фильтры по предметам */} +
+ {subjects.map((subject) => ( + + ))}
-
- {classrooms && classrooms?.length > 0 && ( - -
- {!isHidden && classrooms && classrooms?.length > 0 && ( - - )} - {classrooms?.map((classroom) => ( - - ))} -
-
+ {/* Список кабинетов */} +
+ {isLoading && ( +
+

Загрузка...

+
+ )} + {!isLoading && filteredClassrooms && filteredClassrooms.length > 0 && ( + <> + {!isHidden && filteredClassrooms && filteredClassrooms.length > 0 && ( + + )} + {filteredClassrooms.map((classroom) => ( + + ))} + )} - {classrooms && classrooms.length === 0 && ( + {!isLoading && (!filteredClassrooms || filteredClassrooms.length === 0) && (

- Пригласите учеников — индивидуально или в группу + {searchQuery || selectedSubject !== 'all' + ? 'Ничего не найдено' + : 'Пригласите учеников — индивидуально или в группу'}

)} diff --git a/packages/pages.main/src/ui/components/DateTimeDisplay/DateTimeDisplay.tsx b/packages/pages.main/src/ui/components/DateTimeDisplay/DateTimeDisplay.tsx new file mode 100644 index 00000000..aa431b30 --- /dev/null +++ b/packages/pages.main/src/ui/components/DateTimeDisplay/DateTimeDisplay.tsx @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react'; + +export const DateTimeDisplay = () => { + const [currentDateTime, setCurrentDateTime] = useState(() => { + const now = new Date(); + const time = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); + const weekday = now.toLocaleDateString('ru-RU', { weekday: 'long' }); + const day = now.getDate(); + const month = now.toLocaleDateString('ru-RU', { month: 'long' }); + return { time, date: `${weekday}, ${day} ${month}` }; + }); + + useEffect(() => { + const updateDateTime = () => { + const now = new Date(); + const time = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); + const weekday = now.toLocaleDateString('ru-RU', { weekday: 'long' }); + const day = now.getDate(); + const month = now.toLocaleDateString('ru-RU', { month: 'long' }); + setCurrentDateTime({ time, date: `${weekday}, ${day} ${month}` }); + }; + + updateDateTime(); + const interval = setInterval(updateDateTime, 60000); // Обновляем каждую минуту + + return () => clearInterval(interval); + }, []); + + return ( +
+
{currentDateTime.time}
+
{currentDateTime.date}
+
+ ); +}; diff --git a/packages/pages.main/src/ui/components/DateTimeDisplay/index.ts b/packages/pages.main/src/ui/components/DateTimeDisplay/index.ts new file mode 100644 index 00000000..0b0264b3 --- /dev/null +++ b/packages/pages.main/src/ui/components/DateTimeDisplay/index.ts @@ -0,0 +1 @@ +export { DateTimeDisplay } from './DateTimeDisplay'; diff --git a/packages/pages.main/src/ui/components/Lessons/AllLessons.tsx b/packages/pages.main/src/ui/components/Lessons/AllLessons.tsx new file mode 100644 index 00000000..559f5d53 --- /dev/null +++ b/packages/pages.main/src/ui/components/Lessons/AllLessons.tsx @@ -0,0 +1,64 @@ +import { Badge } from '@xipkg/badge'; +import { UserProfile } from '@xipkg/userprofile'; +import { Clock } from '@xipkg/icons'; + +export const AllLessons = () => { + // TODO: Заменить на реальные данные из API + const lessons = [ + { + id: 1, + time: '15:45 - 16:30', + subject: 'Математика', + studentName: 'Иван Петров', + studentId: 2, + isPaid: true, + }, + { + id: 2, + time: '16:45 - 17:30', + subject: 'Физика', + studentName: 'Анна Кузнецова', + studentId: 3, + isPaid: false, + }, + { + id: 3, + time: '17:45 - 18:30', + subject: 'Химия', + studentName: 'Олег Смирнов', + studentId: 4, + isPaid: true, + }, + { + id: 4, + time: '19:45 - 20:30', + subject: 'История', + studentName: 'Елена Федорова', + studentId: 5, + isPaid: false, + }, + ]; + + return ( +
+ {lessons.map((lesson) => ( +
+
+
{lesson.time}
+
{lesson.subject}
+
+
+ + + + {lesson.isPaid ? 'оплачено' : 'ждёт оплату'} + +
+
+ ))} +
+ ); +}; diff --git a/packages/pages.main/src/ui/components/Lessons/Lessons.tsx b/packages/pages.main/src/ui/components/Lessons/Lessons.tsx index ca914fe0..6c25ed68 100644 --- a/packages/pages.main/src/ui/components/Lessons/Lessons.tsx +++ b/packages/pages.main/src/ui/components/Lessons/Lessons.tsx @@ -1,10 +1,9 @@ import { Button } from '@xipkg/button'; -import { ArrowRight } from '@xipkg/icons'; -import { ScrollArea } from '@xipkg/scrollarea'; -import { Lesson } from './Lesson'; +import { ArrowRight, Group } from '@xipkg/icons'; import { useNavigate, useSearch } from '@tanstack/react-router'; import { Tooltip, TooltipContent, TooltipTrigger } from '@xipkg/tooltip'; -import { AssignLessonButton } from '../AssignLessonButton'; +import { NextLesson } from './NextLesson'; +import { AllLessons } from './AllLessons'; export const Lessons = () => { const navigate = useNavigate(); @@ -24,9 +23,9 @@ export const Lessons = () => { }; return ( -
+
-

Ближайшие занятия

+

Расписание на сегодня

+
-
- -
- {[...new Array(10)].map((_, index) => ( - - ))} -
-
+ +
+
+

Ближайшее занятие

+ +
+ +
+

Все занятие

+ +
); diff --git a/packages/pages.main/src/ui/components/Lessons/NextLesson.tsx b/packages/pages.main/src/ui/components/Lessons/NextLesson.tsx new file mode 100644 index 00000000..6ff98076 --- /dev/null +++ b/packages/pages.main/src/ui/components/Lessons/NextLesson.tsx @@ -0,0 +1,44 @@ +import { Badge } from '@xipkg/badge'; +import { Button } from '@xipkg/button'; +import { UserProfile } from '@xipkg/userprofile'; +import { Conference } from '@xipkg/icons'; + +export const NextLesson = () => { + // TODO: Заменить на реальные данные из API + const lesson = { + id: 1, + time: '14:45 - 15:30', + subject: 'Английский язык', + studentName: 'Мария Сидорова', + studentId: 1, + isPaid: true, + }; + + return ( +
+
+
+
{lesson.time}
+
{lesson.subject}
+
+ + {lesson.isPaid ? 'оплачен' : 'ждёт оплату'} + +
+ +
+ +
+ +
+ + +
+
+ ); +}; diff --git a/packages/pages.main/src/ui/components/Materials/Materials.tsx b/packages/pages.main/src/ui/components/Materials/Materials.tsx index a2fc3500..961ebd15 100644 --- a/packages/pages.main/src/ui/components/Materials/Materials.tsx +++ b/packages/pages.main/src/ui/components/Materials/Materials.tsx @@ -1,78 +1,103 @@ import { Button } from '@xipkg/button'; -import { ArrowRight } from '@xipkg/icons'; -import { ScrollArea } from '@xipkg/scrollarea'; -import { useNavigate, useSearch } from '@tanstack/react-router'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@xipkg/tooltip'; +import { Search } from '@xipkg/icons'; +import { Input } from '@xipkg/input'; import { MaterialsAdd } from 'features.materials.add'; import { useGetMaterialsList } from 'common.services'; import { MaterialsDuplicateProvider, useMaterialsDuplicate } from 'pages.materials'; import { MaterialsDuplicate } from 'features.materials.duplicate'; import { MaterialsCard } from 'features.materials.card'; +import { useState, useMemo } from 'react'; +import { cn } from '@xipkg/utils'; const MaterialsContent = () => { - const navigate = useNavigate(); - const search = useSearch({ strict: false }); const { materialId, open, closeModal, openModal } = useMaterialsDuplicate(); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedFilter, setSelectedFilter] = useState<'all' | 'note' | 'board'>('all'); const { data: materials, isLoading } = useGetMaterialsList({ - content_type: null, // null означает все типы материалов + content_type: selectedFilter === 'all' ? null : selectedFilter === 'note' ? 'note' : 'board', }); - const handleMore = () => { - // Сохраняем параметр call при переходе к материалам - const filteredSearch = search.call ? { call: search.call } : {}; + // Фильтрация материалов по поисковому запросу + const filteredMaterials = useMemo(() => { + if (!materials) return []; - navigate({ - to: '/materials', - search: (prev: Record) => ({ - ...prev, - ...filteredSearch, - }), + return materials.filter((material) => { + if (searchQuery === '') return true; + const query = searchQuery.toLowerCase(); + return material.name?.toLowerCase().includes(query); }); - }; + }, [materials, searchQuery]); + + const filters = [ + { id: 'all' as const, label: 'Все' }, + { id: 'note' as const, label: 'Заметки' }, + { id: 'board' as const, label: 'Доски' }, + ]; return ( <> -
+

Материалы

- - - - - К материалам - - - +
+ +
-
- {materials && materials.length > 0 && ( - + {filters.map((filter) => ( + + ))} +
+ + {/* Поиск */} + } + value={searchQuery} + onChange={(e) => setSearchQuery(e.target.value)} + /> + + {/* Список материалов */} +
+ {isLoading && ( +
+

Загрузка...

+
+ )} + {!isLoading && filteredMaterials && filteredMaterials.length > 0 && ( +
+ {filteredMaterials.map((material) => ( + + ))} +
)} - {materials && materials.length === 0 && ( + {!isLoading && (!filteredMaterials || filteredMaterials.length === 0) && (
-

Здесь пока пусто

+

+ {searchQuery ? 'Ничего не найдено' : 'Здесь пока пусто'} +

)}
diff --git a/packages/pages.main/src/ui/components/Payments/Payments.tsx b/packages/pages.main/src/ui/components/Payments/Payments.tsx index 45fc3b79..aab9c5b4 100644 --- a/packages/pages.main/src/ui/components/Payments/Payments.tsx +++ b/packages/pages.main/src/ui/components/Payments/Payments.tsx @@ -1,35 +1,10 @@ -import { useState } from 'react'; import { useNavigate, useSearch } from '@tanstack/react-router'; import { Button } from '@xipkg/button'; -import { ArrowRight } from '@xipkg/icons'; -import { ScrollArea } from '@xipkg/scrollarea'; -import { Tooltip, TooltipContent, TooltipTrigger } from '@xipkg/tooltip'; -import { - useCurrentUser, - useGetStudentPaymentsList, - useGetTutorPaymentsList, -} from 'common.services'; -import { InvoiceModal } from 'features.invoice'; -import { InvoiceCard } from 'features.invoice.card'; +import { ArrowUpRight, Notification } from '@xipkg/icons'; export const Payments = () => { - const { data: user } = useCurrentUser(); - - const isTutor = user?.default_layout === 'tutor'; - - const { data: studentPayments, isLoading: isLoadingStudent } = useGetStudentPaymentsList({ - disabled: isTutor, - }); - const { data: tutorPayments, isLoading: isLoadingTutor } = useGetTutorPaymentsList({ - disabled: !isTutor, - }); - - const payments = isTutor ? tutorPayments : studentPayments; - const isLoading = isTutor ? isLoadingTutor : isLoadingStudent; - const navigate = useNavigate(); const search = useSearch({ strict: false }); - const [isInvoiceModalOpen, setIsInvoiceModalOpen] = useState(false); const handleMore = () => { // Сохраняем параметр call при переходе к оплатам @@ -44,69 +19,28 @@ export const Payments = () => { }); }; - return ( -
-
-

Оплаты

- - - - - К оплатам - + const handleRemind = () => { + // TODO: Реализовать функцию напоминания об оплате + console.log('Напомнить об оплате'); + }; - {isTutor && ( -
- -
- )} -
-
- {isLoading && ( -
-

Загрузка...

-
- )} - {!isLoading && payments && payments.length > 0 && ( - -
- {payments.map((payment) => ( - - ))} -
-
- )} - {!isLoading && (!payments || payments.length === 0) && ( -
-

Здесь пока пусто

-
- )} + return ( +
+
+ +
- -
); }; diff --git a/packages/pages.main/src/ui/components/index.ts b/packages/pages.main/src/ui/components/index.ts index 083ffb7c..aa5d5314 100644 --- a/packages/pages.main/src/ui/components/index.ts +++ b/packages/pages.main/src/ui/components/index.ts @@ -2,3 +2,5 @@ export { Lessons } from './Lessons'; export { Classrooms } from './Classrooms'; export { Materials } from './Materials'; export { Payments } from './Payments'; +export { ActionButtons } from './ActionButtons'; +export { DateTimeDisplay } from './DateTimeDisplay'; diff --git a/packages/pages.materials/package.json b/packages/pages.materials/package.json index 07a8dea3..f2118d41 100644 --- a/packages/pages.materials/package.json +++ b/packages/pages.materials/package.json @@ -18,7 +18,7 @@ "@xipkg/file": "2.0.12", "@xipkg/scrollarea": "2.2.0", "@xipkg/tabs": "^2.1.0", - "@xipkg/userprofile": "4.0.14", + "@xipkg/userprofile": "4.1.0", "common.api": "*", "common.config": "*", "common.services": "*", diff --git a/packages/pages.payments/package.json b/packages/pages.payments/package.json index 0550569a..7e27deda 100644 --- a/packages/pages.payments/package.json +++ b/packages/pages.payments/package.json @@ -34,7 +34,7 @@ "@xipkg/button": "4.1.0", "@xipkg/dropdown": "3.0.12", "@xipkg/tabs": "^2.1.0", - "@xipkg/userprofile": "4.0.14", + "@xipkg/userprofile": "4.1.0", "@tanstack/react-virtual": "3.13.9", "@xipkg/scrollarea": "2.2.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae2aa2df..9d90879a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1330,8 +1330,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: ^4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: ^1.8.0 version: 1.8.0(react@19.2.4) @@ -1427,8 +1427,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: ^4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: ^1.8.0 version: 1.8.0(react@19.2.4) @@ -2159,8 +2159,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: ^4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: ^1.8.0 version: 1.8.0(react@19.2.4) @@ -2250,8 +2250,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: ^4.0.12 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: ^1.8.0 version: 1.8.0(react@19.2.4) @@ -2643,8 +2643,8 @@ importers: specifier: 2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: 1.8.0 version: 1.8.0(react@19.2.4) @@ -2936,8 +2936,8 @@ importers: specifier: 2.2.0 version: 2.2.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: 1.8.0 version: 1.8.0(react@19.2.4) @@ -3051,8 +3051,8 @@ importers: specifier: 0.2.0 version: 0.2.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: 1.8.0 version: 1.8.0(react@19.2.4) @@ -3178,8 +3178,8 @@ importers: specifier: ^2.0.13 version: 2.0.13(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: 1.8.0 version: 1.8.0(react@19.2.4) @@ -3302,8 +3302,8 @@ importers: specifier: 2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) common.api: specifier: '*' version: link:../common.api @@ -3453,8 +3453,8 @@ importers: specifier: 2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) common.entities: specifier: '*' version: link:../common.entities @@ -3886,8 +3886,8 @@ importers: specifier: 2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: 1.8.0 version: 1.8.0(react@19.2.4) @@ -4022,8 +4022,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) common.api: specifier: '*' version: link:../common.api @@ -4216,8 +4216,8 @@ importers: specifier: ^2.1.0 version: 2.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/userprofile': - specifier: 4.0.14 - version: 4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': specifier: 1.8.0 version: 1.8.0(react@19.2.4) @@ -11232,6 +11232,21 @@ packages: - react-dom dev: false + /@xipkg/avatar@3.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4): + resolution: {integrity: sha512-jmweky9M0k8PoJp/a14PA2vSALfjIa2YyoSnS5Vc3+ZMwVoeD0D5ez72ikppe/Hs3E2GCaKvd8sdzGxIJA4Whw==} + peerDependencies: + react: ^19 + dependencies: + '@radix-ui/react-avatar': 1.1.11(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + '@xipkg/utils': 1.8.0(react@19.2.4) + class-variance-authority: 0.7.1 + react: 19.2.4 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - react-dom + dev: false + /@xipkg/badge@2.0.12(react@19.2.4): resolution: {integrity: sha512-TQXPdo/3pxe/Qb3Z81J4WTkg/OXENFR719YqUvqXzYiHnrtA7lXVsZVHsa1R2fKSf8dvrKKCUOkEQaJISrgU0w==} peerDependencies: @@ -11832,12 +11847,12 @@ packages: resolution: {integrity: sha512-GGAb21wy0l5wuWp7w00lykVNDr2dxPVBtiZ8nCuMHyKKPmsAZrbgyt1OUnn+7AyhnDqQEABS9+i+rsvGvVn/GA==} dev: true - /@xipkg/userprofile@4.0.14(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4): - resolution: {integrity: sha512-vFcIFgYp+99xFP5hGSz3r2+pya8OVq6aph5+37oOYcVBTxshuHmKP7dRM4S6TWhq9BtbvbKUV7ZjbIOIZWnmiA==} + /@xipkg/userprofile@4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4): + resolution: {integrity: sha512-+TzLGqzhi9t7o+SiNs+Tma6E1MXC5MuZf0ygR7+E0JTKJDhf+//EWuJ6G4CDGDSmo/G5Y8gra7UbCLaRKdn8OA==} peerDependencies: react: ^19 dependencies: - '@xipkg/avatar': 3.0.10(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + '@xipkg/avatar': 3.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/utils': 1.8.0(react@19.2.4) class-variance-authority: 0.7.1 react: 19.2.4 From 0ea2009ed04b87f55e880576d04cb8fff7f10704 Mon Sep 17 00:00:00 2001 From: unknownproperty Date: Fri, 20 Feb 2026 12:34:53 +0300 Subject: [PATCH 09/15] feat(modules.navigation): update notifications --- ....svg => navigationlogo-small-dark-new.svg} | 0 ...svg => navigationlogo-small-light-new.svg} | 0 packages/common.ui/src/SmallLogo.tsx | 4 ++-- .../src/ui/Header/Notifications.tsx | 23 +++++++++++-------- .../src/ui/ClassroomsPage.tsx | 2 +- packages/pages.main/src/ui/MainPage.tsx | 22 +++++++++--------- .../pages.materials/src/ui/MaterialsPage.tsx | 2 +- packages/pages.payments/src/ui/Header.tsx | 2 +- 8 files changed, 29 insertions(+), 26 deletions(-) rename apps/xi.web/public/assets/brand/{navigationlogo-small-dark.svg => navigationlogo-small-dark-new.svg} (100%) rename apps/xi.web/public/assets/brand/{navigationlogo-small-light.svg => navigationlogo-small-light-new.svg} (100%) diff --git a/apps/xi.web/public/assets/brand/navigationlogo-small-dark.svg b/apps/xi.web/public/assets/brand/navigationlogo-small-dark-new.svg similarity index 100% rename from apps/xi.web/public/assets/brand/navigationlogo-small-dark.svg rename to apps/xi.web/public/assets/brand/navigationlogo-small-dark-new.svg diff --git a/apps/xi.web/public/assets/brand/navigationlogo-small-light.svg b/apps/xi.web/public/assets/brand/navigationlogo-small-light-new.svg similarity index 100% rename from apps/xi.web/public/assets/brand/navigationlogo-small-light.svg rename to apps/xi.web/public/assets/brand/navigationlogo-small-light-new.svg diff --git a/packages/common.ui/src/SmallLogo.tsx b/packages/common.ui/src/SmallLogo.tsx index 044ead02..79e08e64 100644 --- a/packages/common.ui/src/SmallLogo.tsx +++ b/packages/common.ui/src/SmallLogo.tsx @@ -2,14 +2,14 @@ export const SmallLogo = ({ width = 135, height = 16 }: { width?: number; height return ( <> logo logo { <> - + Уведомления diff --git a/packages/pages.classrooms/src/ui/ClassroomsPage.tsx b/packages/pages.classrooms/src/ui/ClassroomsPage.tsx index 949514b6..b3a554c4 100644 --- a/packages/pages.classrooms/src/ui/ClassroomsPage.tsx +++ b/packages/pages.classrooms/src/ui/ClassroomsPage.tsx @@ -11,7 +11,7 @@ export const ClassroomsPage = () => { return (
-
+

Кабинеты

{isTutor && }
diff --git a/packages/pages.main/src/ui/MainPage.tsx b/packages/pages.main/src/ui/MainPage.tsx index 57c4d5ac..fbdb835a 100644 --- a/packages/pages.main/src/ui/MainPage.tsx +++ b/packages/pages.main/src/ui/MainPage.tsx @@ -1,11 +1,11 @@ /* eslint-disable no-irregular-whitespace */ import { ScrollArea } from '@xipkg/scrollarea'; import { - Materials, - Payments, - Classrooms, - Lessons, - ActionButtons, + // Materials, + // Payments, + // Classrooms, + // Lessons, + // ActionButtons, DateTimeDisplay, } from './components'; import { Menu } from 'common.ui'; @@ -127,28 +127,28 @@ export const MainPage = () => { type="always" className="h-[calc(100vh-64px)] w-full flex-1 overflow-visible lg:overflow-auto" > -
+
{/* Дата и время */} {/* Кнопки действий сверху */} - + {/* */} {/* Две колонки: Расписание слева, Кабинеты справа */} -
+ {/*
-
+
*/} {/* Финансовый блок */} - + {/* */} {/* Материалы */} - {isTutor && } + {/* {isTutor && } */}
diff --git a/packages/pages.materials/src/ui/MaterialsPage.tsx b/packages/pages.materials/src/ui/MaterialsPage.tsx index 97adfcb7..83abebd4 100644 --- a/packages/pages.materials/src/ui/MaterialsPage.tsx +++ b/packages/pages.materials/src/ui/MaterialsPage.tsx @@ -30,7 +30,7 @@ const MaterialsPageContent = () => { return ( <>
-
+
diff --git a/packages/pages.payments/src/ui/Header.tsx b/packages/pages.payments/src/ui/Header.tsx index 21e55107..90ff23b8 100644 --- a/packages/pages.payments/src/ui/Header.tsx +++ b/packages/pages.payments/src/ui/Header.tsx @@ -6,7 +6,7 @@ export const Header = ({ onCreateInvoice }: { onCreateInvoice: () => void }) => const { data: user } = useCurrentUser(); return ( -
+

Контроль оплат

{user?.default_layout === 'tutor' && ( From dfd207e340dbaeb451e11a98207a87fdbbafad11 Mon Sep 17 00:00:00 2001 From: unknownproperty Date: Fri, 20 Feb 2026 18:06:48 +0300 Subject: [PATCH 10/15] feat(modules.navigation): redesign main part first --- packages/common.ui/package.json | 2 +- packages/features.avatar.editor/package.json | 2 +- packages/features.group.invite/package.json | 2 +- packages/features.group.manage/package.json | 2 +- packages/features.invites/package.json | 2 +- packages/features.invoice.card/package.json | 2 +- packages/features.invoice/package.json | 2 +- packages/features.materials.add/package.json | 2 +- packages/features.materials.card/package.json | 2 +- .../src/ui/MaterialsCard.tsx | 2 +- .../features.materials.duplicate/package.json | 2 +- packages/features.students.list/package.json | 2 +- packages/features.table/package.json | 2 +- packages/modules.board/package.json | 2 +- packages/modules.calendar/package.json | 2 +- packages/modules.calendar/src/ui/Calendar.tsx | 2 +- packages/modules.calls/package.json | 2 +- packages/modules.calls/src/ui/Call.tsx | 2 +- .../src/ui/CompactView/CompactView.tsx | 2 +- .../modules.calls/src/ui/PreJoin/PreJoin.tsx | 2 +- packages/modules.editor/package.json | 2 +- packages/modules.modals/package.json | 2 +- packages/modules.navigation/package.json | 2 +- packages/modules.profile/package.json | 2 +- packages/pages.classroom/package.json | 2 +- packages/pages.classrooms/package.json | 2 +- packages/pages.email-confirm/package.json | 2 +- packages/pages.email/package.json | 2 +- packages/pages.main/package.json | 2 +- packages/pages.main/src/ui/MainPage.tsx | 37 +++--- .../ActionButtons/ActionButtons.tsx | 15 ++- .../ui/components/Classrooms/Classroom.tsx | 2 +- .../Classrooms/ClassroomsStudent.tsx | 7 +- .../components/Classrooms/ClassroomsTutor.tsx | 29 ++-- .../src/ui/components/Lessons/Lessons.tsx | 34 +---- .../src/ui/components/Materials/Materials.tsx | 27 ++-- packages/pages.notes/package.json | 2 +- packages/pages.payments/package.json | 2 +- packages/pages.reset-password/package.json | 2 +- packages/pages.signin/package.json | 2 +- packages/pages.signup/package.json | 2 +- packages/pages.welcome/package.json | 2 +- pnpm-lock.yaml | 124 +++++++++--------- 43 files changed, 162 insertions(+), 183 deletions(-) diff --git a/packages/common.ui/package.json b/packages/common.ui/package.json index b62091ae..10a64097 100644 --- a/packages/common.ui/package.json +++ b/packages/common.ui/package.json @@ -14,7 +14,7 @@ "dependencies": { "@tanstack/react-router": "1.128.8", "@xipkg/button": "4.1.0", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/link": "2.0.12", "@xipkg/utils": "1.8.0", "common.env": "*", diff --git a/packages/features.avatar.editor/package.json b/packages/features.avatar.editor/package.json index fa44d78f..b9b33bfa 100644 --- a/packages/features.avatar.editor/package.json +++ b/packages/features.avatar.editor/package.json @@ -20,7 +20,7 @@ "common.ui": "*", "i18next": "24.2.2", "react-i18next": "15.4.1", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/utils": "1.8.0", "@xipkg/slider": "2.0.12", "common.config": "*", diff --git a/packages/features.group.invite/package.json b/packages/features.group.invite/package.json index 841d612b..df09aea9 100644 --- a/packages/features.group.invite/package.json +++ b/packages/features.group.invite/package.json @@ -14,7 +14,7 @@ "@tanstack/react-query": "^5.73.3", "@tanstack/react-router": "^1.128.8", "@xipkg/button": "4.1.0", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "^2.2.9", "@xipkg/modal": "^4.3.1", "@xipkg/scrollarea": "^2.2.0", diff --git a/packages/features.group.manage/package.json b/packages/features.group.manage/package.json index ec158d2d..8f7a1393 100644 --- a/packages/features.group.manage/package.json +++ b/packages/features.group.manage/package.json @@ -14,7 +14,7 @@ "@tanstack/react-query": "^5.73.3", "@tanstack/react-router": "^1.128.8", "@xipkg/button": "4.1.0", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "^2.2.9", "@xipkg/modal": "^4.3.1", "@xipkg/scrollarea": "^2.2.0", diff --git a/packages/features.invites/package.json b/packages/features.invites/package.json index a632ab4c..e4716982 100644 --- a/packages/features.invites/package.json +++ b/packages/features.invites/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@xipkg/button": "4.1.0", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/modal": "^4.3.1", "@xipkg/utils": "^1.8.0", "common.env": "*", diff --git a/packages/features.invoice.card/package.json b/packages/features.invoice.card/package.json index 89914d83..2da800bc 100644 --- a/packages/features.invoice.card/package.json +++ b/packages/features.invoice.card/package.json @@ -18,7 +18,7 @@ "common.api": "*", "common.utils": "*", "common.types": "*", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/button": "4.0.0" }, "devDependencies": { diff --git a/packages/features.invoice/package.json b/packages/features.invoice/package.json index 28c91ac6..23a3c2c8 100644 --- a/packages/features.invoice/package.json +++ b/packages/features.invoice/package.json @@ -15,7 +15,7 @@ "@tanstack/react-router": "1.120.11", "@xipkg/button": "4.1.0", "@xipkg/form": "4.2.1", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "2.2.9", "@xipkg/modal": "^4.3.1", "@xipkg/select": "2.2.5", diff --git a/packages/features.materials.add/package.json b/packages/features.materials.add/package.json index c2c9103c..3ad151ff 100644 --- a/packages/features.materials.add/package.json +++ b/packages/features.materials.add/package.json @@ -13,7 +13,7 @@ "dependencies": { "@xipkg/button": "4.1.0", "@xipkg/dropdown": "^3.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/utils": "^1.8.0" }, "devDependencies": { diff --git a/packages/features.materials.card/package.json b/packages/features.materials.card/package.json index e8d629e6..0bc60f53 100644 --- a/packages/features.materials.card/package.json +++ b/packages/features.materials.card/package.json @@ -15,7 +15,7 @@ "@xipkg/button": "4.1.0", "@xipkg/badge": "2.0.12", "@xipkg/dropdown": "^3.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/utils": "^1.8.0", "common.services": "*", "common.types": "*", diff --git a/packages/features.materials.card/src/ui/MaterialsCard.tsx b/packages/features.materials.card/src/ui/MaterialsCard.tsx index 289dd8fd..f3bf3fc0 100644 --- a/packages/features.materials.card/src/ui/MaterialsCard.tsx +++ b/packages/features.materials.card/src/ui/MaterialsCard.tsx @@ -53,7 +53,7 @@ export const MaterialsCard = ({
{ }, []); return ( -
+

diff --git a/packages/modules.calls/package.json b/packages/modules.calls/package.json index ee8a3d2f..3dda401a 100644 --- a/packages/modules.calls/package.json +++ b/packages/modules.calls/package.json @@ -27,7 +27,7 @@ "@xipkg/breadcrumbs": "2.0.13", "@xipkg/button": "4.1.0", "@xipkg/dropdown": "3.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/modal": "^4.3.1", "@xipkg/scrollarea": "2.2.0", "@xipkg/select": "2.2.5", diff --git a/packages/modules.calls/src/ui/Call.tsx b/packages/modules.calls/src/ui/Call.tsx index 0d1f80d1..8654ee5c 100644 --- a/packages/modules.calls/src/ui/Call.tsx +++ b/packages/modules.calls/src/ui/Call.tsx @@ -27,7 +27,7 @@ export const Call = () => { }, [pathname, mode, updateStore]); return ( -

+
{isStarted ? (
diff --git a/packages/modules.calls/src/ui/CompactView/CompactView.tsx b/packages/modules.calls/src/ui/CompactView/CompactView.tsx index 1beed5ab..01251477 100644 --- a/packages/modules.calls/src/ui/CompactView/CompactView.tsx +++ b/packages/modules.calls/src/ui/CompactView/CompactView.tsx @@ -108,7 +108,7 @@ export const Compact: FC = ({ children }) => { return ( -
+
{ return ( <> -
+
diff --git a/packages/modules.editor/package.json b/packages/modules.editor/package.json index 2f132b77..d8122e76 100644 --- a/packages/modules.editor/package.json +++ b/packages/modules.editor/package.json @@ -35,7 +35,7 @@ "@xipkg/button": "4.1.0", "@xipkg/dropdown": "^3.0.12", "@xipkg/file": "^2.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "2.2.9", "@xipkg/popover": "^2.1.0", "@xipkg/select": "^2.2.5", diff --git a/packages/modules.modals/package.json b/packages/modules.modals/package.json index 97255203..e2ce85be 100644 --- a/packages/modules.modals/package.json +++ b/packages/modules.modals/package.json @@ -18,7 +18,7 @@ "react-i18next": "15.4.1", "@tanstack/react-router": "^1.128.8", "@xipkg/button": "4.1.0", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/link": "2.0.12", "@xipkg/utils": "1.8.0", "@xipkg/scrollarea": "2.2.0", diff --git a/packages/modules.navigation/package.json b/packages/modules.navigation/package.json index 0cfa62b3..36d43896 100644 --- a/packages/modules.navigation/package.json +++ b/packages/modules.navigation/package.json @@ -15,7 +15,7 @@ "@xipkg/avatar": "3.0.10", "@xipkg/button": "4.1.0", "@xipkg/dropdown": "3.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/link": "2.0.12", "@xipkg/scrollarea": "2.2.0", "@xipkg/radio": "^2.0.6", diff --git a/packages/modules.profile/package.json b/packages/modules.profile/package.json index 142c3b30..f7b4cc68 100644 --- a/packages/modules.profile/package.json +++ b/packages/modules.profile/package.json @@ -15,7 +15,7 @@ "@xipkg/badge": "2.0.12", "@xipkg/button": "4.1.0", "@xipkg/dropdown": "3.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/modal": "^4.3.1", "@xipkg/select": "2.2.5", "@xipkg/toggle": "^2.0.13", diff --git a/packages/pages.classroom/package.json b/packages/pages.classroom/package.json index c6ae2feb..d4533e64 100644 --- a/packages/pages.classroom/package.json +++ b/packages/pages.classroom/package.json @@ -16,7 +16,7 @@ "@xipkg/command": "1.0.0", "@xipkg/dropdown": "3.0.12", "@xipkg/form": "4.2.1", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "2.2.9", "@xipkg/link": "^2.0.12", "@xipkg/scrollarea": "2.2.0", diff --git a/packages/pages.classrooms/package.json b/packages/pages.classrooms/package.json index c1ab122b..fd55fa82 100644 --- a/packages/pages.classrooms/package.json +++ b/packages/pages.classrooms/package.json @@ -16,7 +16,7 @@ "@xipkg/badge": "2.0.12", "@xipkg/button": "4.1.0", "@xipkg/dropdown": "3.0.12", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "2.2.9", "@xipkg/link": "^2.0.12", "@xipkg/modal": "^4.3.1", diff --git a/packages/pages.email-confirm/package.json b/packages/pages.email-confirm/package.json index 2a506c60..d043d1e1 100644 --- a/packages/pages.email-confirm/package.json +++ b/packages/pages.email-confirm/package.json @@ -16,7 +16,7 @@ "@xipkg/button": "4.1.0", "@xipkg/fileuploader": "^2.0.12", "@xipkg/form": "4.2.1", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "2.0.7", "@xipkg/utils": "1.8.0", "common.auth": "*", diff --git a/packages/pages.email/package.json b/packages/pages.email/package.json index b26658be..8e81c731 100644 --- a/packages/pages.email/package.json +++ b/packages/pages.email/package.json @@ -17,7 +17,7 @@ "@xipkg/button": "4.1.0", "@xipkg/fileuploader": "^2.0.12", "@xipkg/form": "4.2.1", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/input": "2.0.7", "@xipkg/utils": "1.8.0", "common.auth": "*", diff --git a/packages/pages.main/package.json b/packages/pages.main/package.json index e23434ae..2f9c5193 100644 --- a/packages/pages.main/package.json +++ b/packages/pages.main/package.json @@ -16,7 +16,7 @@ "@xipkg/alert": "1.1.0", "@xipkg/badge": "2.0.12", "@xipkg/button": "4.1.0", - "@xipkg/icons": "^3.0.3", + "@xipkg/icons": "^3.0.4", "@xipkg/scrollarea": "2.2.0", "@xipkg/tooltip": "2.1.0", "@xipkg/userprofile": "4.1.0", diff --git a/packages/pages.main/src/ui/MainPage.tsx b/packages/pages.main/src/ui/MainPage.tsx index fbdb835a..1bf4c1af 100644 --- a/packages/pages.main/src/ui/MainPage.tsx +++ b/packages/pages.main/src/ui/MainPage.tsx @@ -1,11 +1,11 @@ /* eslint-disable no-irregular-whitespace */ import { ScrollArea } from '@xipkg/scrollarea'; import { - // Materials, - // Payments, - // Classrooms, - // Lessons, - // ActionButtons, + Materials, + Payments, + Classrooms, + Lessons, + ActionButtons, DateTimeDisplay, } from './components'; import { Menu } from 'common.ui'; @@ -122,33 +122,26 @@ export const MainPage = () => { ]; return ( -
- +
+
{/* Дата и время */} - {/* Кнопки действий сверху */} - {/* */} - - {/* Две колонки: Расписание слева, Кабинеты справа */} - {/*
+ {/* Три колонки: Расписание, Кабинеты, Payments и Materials */} +
-
*/} - - {/* Финансовый блок */} - {/* */} - - {/* Материалы */} - {/* {isTutor && } */} +
+ + + {isTutor && } +
+
diff --git a/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx b/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx index 7d927fb1..9f5f812e 100644 --- a/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx +++ b/packages/pages.main/src/ui/components/ActionButtons/ActionButtons.tsx @@ -2,6 +2,7 @@ import { Button } from '@xipkg/button'; import { ModalInvitation } from 'features.invites'; import { ModalAddGroup } from 'features.group.add'; import { useCurrentUser } from 'common.services'; +import { Group, UserPlus } from '@xipkg/icons'; export const ActionButtons = () => { const { data: user } = useCurrentUser(); @@ -12,15 +13,16 @@ export const ActionButtons = () => { } return ( -
+
@@ -28,11 +30,12 @@ export const ActionButtons = () => { diff --git a/packages/pages.main/src/ui/components/Classrooms/Classroom.tsx b/packages/pages.main/src/ui/components/Classrooms/Classroom.tsx index 90ec5ead..f05d9bff 100644 --- a/packages/pages.main/src/ui/components/Classrooms/Classroom.tsx +++ b/packages/pages.main/src/ui/components/Classrooms/Classroom.tsx @@ -90,7 +90,7 @@ export const Classroom = ({ classroom, isLoading }: ClassroomProps) => { }; return ( -
+
- - К календарю -
- - - - + + + + Назначение урока + + + + + + + + ); diff --git a/packages/features.lesson.add/src/ui/components/AddingForm.tsx b/packages/features.lesson.add/src/ui/components/AddingForm.tsx index 5dc8778d..9f67488a 100644 --- a/packages/features.lesson.add/src/ui/components/AddingForm.tsx +++ b/packages/features.lesson.add/src/ui/components/AddingForm.tsx @@ -1,4 +1,3 @@ -import { useEffect } from 'react'; import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@xipkg/form'; import { Input } from '@xipkg/input'; import { useMaskInput } from '@xipkg/inputmask'; @@ -13,11 +12,10 @@ 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(); +export const AddingForm: FC = ({ children, onClose }) => { + const { form, control, handleSubmit, handleClearForm, onSubmit } = useAddingForm(); const maskRefStartTime = useMaskInput('time'); const maskRefEndTime = useMaskInput('time'); @@ -32,12 +30,6 @@ export const AddingForm: FC = ({ children, onClose, onDateChang onClose(); }; - useEffect(() => { - if (eventDate) { - onDateChange(eventDate); - } - }, [eventDate, onDateChange]); - return ( = ({ children, currentDay, className }) => { - return ( -
-
- -
-
- Назначение занятия - {children} -
-
- ); -}; - -const ModalContentMobile: FC = ({ children, currentDay, className }) => { - return ( -
- Назначение занятия -
-
- -
-
- {children} -
-
-
- ); -}; - -export const ModalContentWrapper: FC = ({ children, currentDay }) => { - return ( - <> - - - - ); -}; diff --git a/packages/pages.main/src/ui/components/Lessons/Lessons.tsx b/packages/pages.main/src/ui/components/Lessons/Lessons.tsx index eef7b27f..d05e1761 100644 --- a/packages/pages.main/src/ui/components/Lessons/Lessons.tsx +++ b/packages/pages.main/src/ui/components/Lessons/Lessons.tsx @@ -2,33 +2,45 @@ import { Button } from '@xipkg/button'; import { Group } from '@xipkg/icons'; import { NextLesson } from './NextLesson'; import { AllLessons } from './AllLessons'; +import { AddingLessonModal } from 'features.lesson.add'; +import { useState } from 'react'; export const Lessons = () => { + const [open, setOpen] = useState(false); + + const handleOpenAddingModal = () => { + setOpen(true); + }; + return ( -
-
-

Расписание на сегодня

-
- + <> + +
+
+

Расписание на сегодня

+
+ +
-
-
-
-

Ближайшее занятие

- -
+
+
+

Ближайшее занятие

+ +
-
-

Все занятие

- +
+

Все занятие

+ +
-
+ ); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b60a9160..8db957f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1761,8 +1761,8 @@ importers: specifier: 1.120.11 version: 1.120.11(react-dom@19.2.4)(react@19.2.4) '@xipkg/button': - specifier: 3.2.0 - version: 3.2.0(@types/react@19.2.14)(react@19.2.4) + specifier: 4.1.0 + version: 4.1.0(@types/react@19.2.14)(react@19.2.4) '@xipkg/datepicker': specifier: 2.2.0 version: 2.2.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) @@ -1770,8 +1770,8 @@ importers: specifier: 4.2.1 version: 4.2.1(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/icons': - specifier: ^2.5.4 - version: 2.7.2(react@19.2.4) + specifier: ^3.0.4 + version: 3.0.4(react@19.2.4) '@xipkg/input': specifier: 2.2.9 version: 2.2.9(react@19.2.4) @@ -1779,8 +1779,8 @@ importers: specifier: 2.0.12 version: 2.0.12(react-dom@19.2.4)(react@19.2.4) '@xipkg/modal': - specifier: 4.1.0 - version: 4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) + specifier: 4.3.1 + version: 4.3.1(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) '@xipkg/select': specifier: 2.2.5 version: 2.2.5(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) @@ -11688,22 +11688,6 @@ packages: react: 19.2.4 dev: false - /@xipkg/modal@4.1.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4): - resolution: {integrity: sha512-YVuwzTqFaTgouBd7EldpUYPpd3W3hE78LISDjFv9+f9w0rqRpMFhVKQpHpo8WdVb1ahwKDiH14ZssxzkNzKBUA==} - peerDependencies: - react: ^19 - dependencies: - '@radix-ui/react-dialog': 1.1.6(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4) - '@xipkg/icons': 2.7.2(react@19.2.4) - '@xipkg/utils': 1.8.0(react@19.2.4) - lucide-react: 0.475.0(react@19.2.4) - react: 19.2.4 - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - react-dom - dev: false - /@xipkg/modal@4.2.0(@types/react-dom@19.2.3)(@types/react@19.2.14)(react-dom@19.2.4)(react@19.2.4): resolution: {integrity: sha512-5uax+K526AJCkLP51+8XfFIefWVhPK0tG73Ji6gaYxrVt6zAiplReMgYkzXBwxXMMgBClYSw4+4juWB1UiGQ4g==} peerDependencies: From dd9370fb65fe4d564db3e5efea7075f06b17e29d Mon Sep 17 00:00:00 2001 From: unknownproperty Date: Sat, 21 Feb 2026 02:42:03 +0300 Subject: [PATCH 15/15] fix(features.lesson.add): some improves --- packages/features.lesson.add/README.md | 30 ---- packages/features.lesson.add/package.json | 1 + .../src/hooks/useAddingForm.ts | 32 ++-- .../src/hooks/useConstants.ts | 12 +- .../src/ui/AddingLessonModal.tsx | 4 +- .../src/ui/components/AddingForm.tsx | 17 +- .../src/ui/components/DayCalendar.tsx | 152 ------------------ .../src/ui/components/StudentSelector.tsx | 17 +- .../features.lesson.add/src/utils/utils.ts | 12 +- 9 files changed, 58 insertions(+), 219 deletions(-) delete mode 100644 packages/features.lesson.add/README.md delete mode 100644 packages/features.lesson.add/src/ui/components/DayCalendar.tsx diff --git a/packages/features.lesson.add/README.md b/packages/features.lesson.add/README.md deleted file mode 100644 index 9d07398e..00000000 --- a/packages/features.lesson.add/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Модуль создания счетов на оплату (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/package.json b/packages/features.lesson.add/package.json index 4017e23e..3936700a 100644 --- a/packages/features.lesson.add/package.json +++ b/packages/features.lesson.add/package.json @@ -13,6 +13,7 @@ "dependencies": { "@tanstack/react-router": "1.120.11", "@tanstack/react-query": "^5.73.3", + "react-i18next": "15.4.1", "sonner": "^1.4.0", "common.services": "*", "common.utils": "*", diff --git a/packages/features.lesson.add/src/hooks/useAddingForm.ts b/packages/features.lesson.add/src/hooks/useAddingForm.ts index 4441b48e..b0d8fd18 100644 --- a/packages/features.lesson.add/src/hooks/useAddingForm.ts +++ b/packages/features.lesson.add/src/hooks/useAddingForm.ts @@ -1,5 +1,6 @@ import { useForm } from '@xipkg/form'; import { zodResolver } from '@hookform/resolvers/zod'; +import { toast } from 'sonner'; import { formSchema, type FormData } from '../model/formSchema'; import { useFetchClassrooms } from 'common.services'; @@ -14,7 +15,7 @@ const DEFAULT_VALUES: FormData = { }; export const useAddingForm = () => { - const { data: classrooms } = useFetchClassrooms(); + const { data: classrooms, isLoading: isClassroomsLoading } = useFetchClassrooms(); const form = useForm({ resolver: zodResolver(formSchema), @@ -22,45 +23,40 @@ export const useAddingForm = () => { defaultValues: DEFAULT_VALUES, }); - const { control, handleSubmit, setValue, formState, watch } = form; - const eventDate = watch('startDate'); + const { control, handleSubmit, reset } = form; const onSubmit = (data: FormData) => { - const student = classrooms?.find((c) => c.id === Number(data.studentId)); - - const student_ids = student?.kind === 'individual' ? [student.student_id] : []; + const classroom = classrooms?.find((c) => c.id === Number(data.studentId)); + const studentIds = classroom?.kind === 'individual' ? [classroom.student_id] : []; const payload = { title: data.title, - description: data.description || '', - student_ids, + description: data.description ?? '', + studentIds, startTime: data.startTime, endTime: data.endTime, startDate: data.startDate, shouldRepeat: data.shouldRepeat, }; - console.log('errors', formState.errors); - console.log('payload', payload); + + // TODO: подключить API создания урока (common.api или модуль календаря) + // await createLesson(payload); + toast.success('Урок назначен'); }; 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); + reset(DEFAULT_VALUES); }; return { form, control, - eventDate, handleSubmit, onSubmit, handleClearForm, + classrooms: classrooms ?? [], + isClassroomsLoading, }; }; diff --git a/packages/features.lesson.add/src/hooks/useConstants.ts b/packages/features.lesson.add/src/hooks/useConstants.ts index bfe065c9..97ecd41d 100644 --- a/packages/features.lesson.add/src/hooks/useConstants.ts +++ b/packages/features.lesson.add/src/hooks/useConstants.ts @@ -11,12 +11,12 @@ export const useConstants = () => { 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')}` }, + { 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]); diff --git a/packages/features.lesson.add/src/ui/AddingLessonModal.tsx b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx index 44bbecba..96e253e7 100644 --- a/packages/features.lesson.add/src/ui/AddingLessonModal.tsx +++ b/packages/features.lesson.add/src/ui/AddingLessonModal.tsx @@ -33,10 +33,10 @@ export const AddingLessonModal = ({ open, onOpenChange }: AddingLessonModalProps - - diff --git a/packages/features.lesson.add/src/ui/components/AddingForm.tsx b/packages/features.lesson.add/src/ui/components/AddingForm.tsx index 9f67488a..bdade425 100644 --- a/packages/features.lesson.add/src/ui/components/AddingForm.tsx +++ b/packages/features.lesson.add/src/ui/components/AddingForm.tsx @@ -15,7 +15,15 @@ interface AddingFormProps extends PropsWithChildren { } export const AddingForm: FC = ({ children, onClose }) => { - const { form, control, handleSubmit, handleClearForm, onSubmit } = useAddingForm(); + const { + form, + control, + handleSubmit, + handleClearForm, + onSubmit, + classrooms, + isClassroomsLoading, + } = useAddingForm(); const maskRefStartTime = useMaskInput('time'); const maskRefEndTime = useMaskInput('time'); @@ -33,6 +41,7 @@ export const AddingForm: FC = ({ children, onClose }) => { return ( = ({ children, onClose }) => { Ученик или группа - + diff --git a/packages/features.lesson.add/src/ui/components/DayCalendar.tsx b/packages/features.lesson.add/src/ui/components/DayCalendar.tsx deleted file mode 100644 index 6bfd4001..00000000 --- a/packages/features.lesson.add/src/ui/components/DayCalendar.tsx +++ /dev/null @@ -1,152 +0,0 @@ -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/StudentSelector.tsx b/packages/features.lesson.add/src/ui/components/StudentSelector.tsx index d4bbd05e..c8ca1f35 100644 --- a/packages/features.lesson.add/src/ui/components/StudentSelector.tsx +++ b/packages/features.lesson.add/src/ui/components/StudentSelector.tsx @@ -1,21 +1,26 @@ import { Select, SelectValue, SelectTrigger, SelectContent, SelectItem } from '@xipkg/select'; -import { useFetchClassrooms } from 'common.services'; +import type { ClassroomT } from 'common.api'; type StudentSelectorProps = { value: string; onChange: (value: string) => void; + classrooms: ClassroomT[]; + isLoading?: boolean; }; -export const StudentSelector = ({ value, onChange }: StudentSelectorProps) => { - const { data: classrooms, isLoading } = useFetchClassrooms(); - +export const StudentSelector = ({ + value, + onChange, + classrooms, + isLoading, +}: StudentSelectorProps) => { return ( - - {classrooms?.map((classroom) => ( + {classrooms.map((classroom) => ( { - const weekDayName = date.toLocaleDateString('ru-RU', { weekday: format }); - const monthName = date.toLocaleDateString('ru-RU', { month: 'long' }); +const DEFAULT_LOCALE = 'ru-RU'; + +export const getFullDateString = ( + date: Date, + format: 'short' | 'long' = 'short', + locale: string = DEFAULT_LOCALE, +) => { + const weekDayName = date.toLocaleDateString(locale, { weekday: format }); + const monthName = date.toLocaleDateString(locale, { month: 'long' }); return `${weekDayName} ${date.getDate()} ${monthName}`; };