From 741e4f7db711d58baa938e6ddb9ab6ffae4a3776 Mon Sep 17 00:00:00 2001 From: antigravity-xd Date: Sat, 11 Apr 2026 01:58:11 +0300 Subject: [PATCH] feat(ui): Enable full UI functionality check - Expanded mock API with rich data for all sections - Implemented functional Docs and Support pages - Added Message detail view with history and attachments - Refined routing to map all sidebar destinations - Finalized M3/xd-client hybrid styling across all pages Co-Authored-By: Claude --- src/App.tsx | 13 ++--- src/api/mock.ts | 41 ++++++++++++---- src/pages/Docs.tsx | 59 +++++++++++++++++++++++ src/pages/Messages.tsx | 105 ++++++++++++++++++++++++++++++++++++++++- src/pages/Support.tsx | 70 +++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 21 deletions(-) create mode 100644 src/pages/Docs.tsx create mode 100644 src/pages/Support.tsx diff --git a/src/App.tsx b/src/App.tsx index 2dbd619..64495dc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,8 @@ import { Messages } from './pages/Messages'; import { Schedule } from './pages/Schedule'; import { Grades } from './pages/Grades'; import { Debts } from './pages/Debts'; +import { Docs } from './pages/Docs'; +import { Support } from './pages/Support'; import { useStore } from './store/useStore'; import { ThemeProvider } from './components/ThemeProvider'; import { Layout } from './components/Layout'; @@ -21,13 +23,6 @@ const ProtectedRoute: React.FC<{ children: React.ReactNode }> = ({ children }) = return {children}; }; -const PlaceholderPage: React.FC<{ title: string }> = ({ title }) => ( -
-

{title}

-

Этот раздел появится в будущих обновлениях

-
-); - function App() { return ( @@ -70,7 +65,7 @@ function App() { path="/docs" element={ - + } /> @@ -78,7 +73,7 @@ function App() { path="/support" element={ - + } /> diff --git a/src/api/mock.ts b/src/api/mock.ts index bac270f..347e512 100644 --- a/src/api/mock.ts +++ b/src/api/mock.ts @@ -9,28 +9,41 @@ export const mockProfile: Profile = { raw: { 'Дата рождения': '01.01.2004', 'Статус': 'Студент', + 'Курс': '2', + 'Форма обучения': 'Очная', }, }; export const mockMessages: MessageListItem[] = [ - { id: '1', date: '10.04.2026', subject: 'Оплата обучения', senderOrRecipient: 'Бухгалтерия', hasFiles: true }, - { id: '2', date: '09.04.2026', subject: 'Пересдача экзамена', senderOrRecipient: 'Деканат ИСС', hasFiles: false }, - { id: '3', date: '08.04.2026', subject: 'Приглашение на конференцию', senderOrRecipient: 'Студсовет', hasFiles: false }, + { id: '1', date: '10.04.2026', subject: 'Оплата обучения за 4 семестр', senderOrRecipient: 'Бухгалтерия', hasFiles: true }, + { id: '2', date: '09.04.2026', subject: 'Пересдача экзамена по Физике', senderOrRecipient: 'Деканат ИСС', hasFiles: false }, + { id: '3', date: '08.04.2026', subject: 'Приглашение на конференцию "Сети 2026"', senderOrRecipient: 'Студсовет', hasFiles: false }, + { id: '4', date: '07.04.2026', subject: 'Заполнение анкеты первокурсника', senderOrRecipient: 'Отдел кадров', hasFiles: true }, + { id: '5', date: '05.04.2026', subject: 'Изменение в расписании на 12 апреля', senderOrRecipient: 'Учебный отдел', hasFiles: false }, ]; export const mockSchedule: Schedule = { days: [ { - date: '13.04.2026, Понедельник', + date: '13.04.2026, Понедельник (Числитель)', lessons: [ { time: '09:00 - 10:35', subject: 'Высшая математика', type: 'Лекция', teacher: 'Петров П.П.', room: '123/1' }, { time: '10:45 - 12:20', subject: 'Физика', type: 'Практика', teacher: 'Сидоров С.С.', room: '456/2' }, + { time: '13:00 - 14:35', subject: 'Информационные технологии', type: 'Лабораторная', teacher: 'Николаев Н.Н.', room: '302/1' }, ] }, { date: '14.04.2026, Вторник', lessons: [ { time: '13:00 - 14:35', subject: 'Иностранный язык', type: 'Практика', teacher: 'Smith J.', room: '222/1' }, + { time: '14:45 - 16:20', subject: 'Физкультура', type: 'Практика', teacher: 'Зайцев А.В.', room: 'Спортзал' }, + ] + }, + { + date: '15.04.2026, Среда', + lessons: [ + { time: '09:00 - 10:35', subject: 'Экология', type: 'Лекция', teacher: 'Зеленина Е.С.', room: '501/2' }, + { time: '10:45 - 12:20', subject: 'История России', type: 'Лекция', teacher: 'Александров А.А.', room: 'Актовый зал' }, ] } ] @@ -38,12 +51,19 @@ export const mockSchedule: Schedule = { export const mockGrades: Grades = { entries: [ - { subject: 'Информатика', type: 'Экзамен', mark: 'Отлично', teacher: 'Николаев Н.Н.', semester: '1' }, + { subject: 'Математический анализ', type: 'Экзамен', mark: 'Отлично', teacher: 'Петров П.П.', semester: '1' }, { subject: 'История', type: 'Зачет', mark: 'Зачтено', teacher: 'Александров А.А.', semester: '1' }, - { subject: 'Математический анализ', type: 'Экзамен', mark: 'Хорошо', teacher: 'Петров П.П.', semester: '1' }, + { subject: 'Информатика', type: 'Экзамен', mark: 'Отлично', teacher: 'Николаев Н.Н.', semester: '1' }, + { subject: 'Физика (Механика)', type: 'Дифф. зачет', mark: 'Хорошо', teacher: 'Сидоров С.С.', semester: '1' }, + { subject: 'Основы программирования', type: 'Экзамен', mark: 'Отлично', teacher: 'Николаев Н.Н.', semester: '2' }, + { subject: 'Электротехника', type: 'Экзамен', mark: 'Удовл.', teacher: 'Токов И.А.', semester: '2' }, ] }; +export const mockDebts: GradeEntry[] = [ + { subject: 'Философия', type: 'Зачет', mark: 'Не зачтено', teacher: 'Мудров Ф.Ф.', semester: '2' }, +]; + export const mockApi = { getProfile: async (): Promise => { return new Promise((resolve) => { @@ -64,10 +84,11 @@ export const mockApi = { return new Promise((resolve) => { setTimeout(() => resolve({ id, - text: 'Текст сообщения от бэкенда. Пожалуйста, обратите внимание на сроки.', + text: 'Добрый день!\n\nИнформируем вас о необходимости произвести оплату за 4 семестр обучения до 15 апреля 2026 года. В случае возникновения вопросов, пожалуйста, свяжитесь с бухгалтерией по внутреннему номеру 102.\n\nС уважением, Администрация.', history: [ - { date: '10.04.2026', author: 'Бухгалтерия', text: 'Первое сообщение в истории.' } - ] + { date: '10.04.2026', author: 'Бухгалтерия', text: 'Уведомление об оплате' } + ], + files: ['receipt_form.pdf', 'payment_instruction.docx'] }), 500); }); }, @@ -83,7 +104,7 @@ export const mockApi = { }, getDebts: async (): Promise<{ entries: GradeEntry[] }> => { return new Promise((resolve) => { - setTimeout(() => resolve({ entries: [] }), 500); + setTimeout(() => resolve({ entries: mockDebts }), 500); }); }, }; diff --git a/src/pages/Docs.tsx b/src/pages/Docs.tsx new file mode 100644 index 0000000..4b7db94 --- /dev/null +++ b/src/pages/Docs.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { FileText, FileCheck, FileSignature, Download, ExternalLink } from 'lucide-react'; + +const docs = [ + { id: '1', title: 'Справка об обучении', description: 'Для предоставления по месту требования', icon: FileCheck }, + { id: '2', title: 'Справка о доходах (стипендия)', description: 'Для получения социальных льгот', icon: FileText }, + { id: '3', title: 'Заявление на мат. помощь', description: 'Подача заявления в профком', icon: FileSignature }, + { id: '4', title: 'Заявление на смену группы', description: 'Перевод в другую учебную группу', icon: FileSignature }, +]; + +export const Docs: React.FC = () => { + return ( +
+
+
+ +
+

Документы

+
+ +
+ {docs.map((doc) => ( +
+
+
+ +
+ +
+
+

{doc.title}

+

{doc.description}

+
+
+ +
+
+ ))} +
+ +
+
+
+ +
+
+

Готовые документы

+

Здесь будут отображаться документы, которые вы заказывали ранее и они уже готовы.

+
+
+
+
+ ); +}; diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index 8bc3699..94a99a7 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -1,14 +1,18 @@ import React, { useEffect, useState } from 'react'; import { api } from '../api/client'; -import type { MessageListItem } from '../types/api'; -import { Mail, Send, Paperclip, ChevronRight, Inbox, MessageSquareText } from 'lucide-react'; +import type { MessageListItem, MessageDetails } from '../types/api'; +import { Mail, Send, Paperclip, ChevronRight, Inbox, MessageSquareText, ArrowLeft, Download, Reply } from 'lucide-react'; import { cn } from '@/lib/utils'; import { Badge } from '../components/ui/badge'; +import { Button } from '../components/ui/button'; export const Messages: React.FC = () => { const [messages, setMessages] = useState([]); + const [selectedId, setSelectedId] = useState(null); + const [details, setDetails] = useState(null); const [type, setType] = useState<'in' | 'out'>('in'); const [loading, setLoading] = useState(true); + const [detailsLoading, setDetailsLoading] = useState(false); useEffect(() => { const fetchMessages = async () => { @@ -25,6 +29,102 @@ export const Messages: React.FC = () => { fetchMessages(); }, [type]); + const handleSelectMessage = async (id: string) => { + setSelectedId(id); + setDetailsLoading(true); + try { + const data = await api.getMessageDetails(id); + setDetails(data); + } catch (err) { + console.error(err); + } finally { + setDetailsLoading(false); + } + }; + + const handleBack = () => { + setSelectedId(null); + setDetails(null); + }; + + if (selectedId) { + return ( +
+ + + {detailsLoading ? ( +
+
+

Загрузка содержания...

+
+ ) : details ? ( +
+
+
+
+

{messages.find(m => m.id === selectedId)?.subject}

+

+ От: {messages.find(m => m.id === selectedId)?.senderOrRecipient} • {messages.find(m => m.id === selectedId)?.date} +

+
+ +
+ +
+ {details.text} +
+ + {details.files && details.files.length > 0 && ( +
+

Вложения ({details.files.length})

+
+ {details.files.map((file, i) => ( +
+
+ +
+ {file} + +
+ ))} +
+
+ )} +
+ + {details.history && details.history.length > 0 && ( +
+

История переписки

+ {details.history.map((h, i) => ( +
+
+ {h.author} + {h.date} +
+

{h.text}

+
+ ))} +
+ )} +
+ ) : ( +
+ Не удалось загрузить сообщение +
+ )} +
+ ); + } + return (
@@ -75,6 +175,7 @@ export const Messages: React.FC = () => { messages.map((msg) => (
handleSelectMessage(msg.id)} className="surface-card group hover:bg-card/60 cursor-pointer flex items-center gap-4 p-4" >
diff --git a/src/pages/Support.tsx b/src/pages/Support.tsx new file mode 100644 index 0000000..f93d553 --- /dev/null +++ b/src/pages/Support.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { LifeBuoy, Send, MessageCircle, HelpCircle } from 'lucide-react'; +import { TextField } from '../components/TextField'; +import { Button } from '../components/ui/button'; + +export const Support: React.FC = () => { + return ( +
+
+
+ +
+

Поддержка

+
+ +
+
+
+

+ + Новое обращение +

+

Опишите вашу проблему, и мы ответим вам в течение рабочего дня.

+ +
e.preventDefault()}> + +
+ +