diff --git a/src/api/client.ts b/src/api/client.ts index a28fff7..82110e2 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -48,6 +48,30 @@ export const api = { const response = await client.get>(`/v1/messages/${id}`); return response.data.data; }, + getRecipients: async () => { + if (useStore.getState().isMockMode) { + return mockApi.getRecipients(); + } + const client = getClient(); + const response = await client.get>('/v1/messages/recipients'); + return response.data.data; + }, + sendMessage: async (recipientId: string, subject: string, text: string) => { + if (useStore.getState().isMockMode) { + return mockApi.sendMessage(recipientId, subject, text); + } + const client = getClient(); + const response = await client.post('/v1/messages', { recipientId, subject, text }); + return response.data; + }, + replyToMessage: async (id: string, text: string, item?: string, idinfo?: string) => { + if (useStore.getState().isMockMode) { + return mockApi.replyToMessage(id, text, item, idinfo); + } + const client = getClient(); + const response = await client.post(`/v1/messages/${id}/reply`, { text, item, idinfo }); + return response.data; + }, getSchedule: async () => { if (useStore.getState().isMockMode) { return mockApi.getSchedule(); diff --git a/src/api/mock.ts b/src/api/mock.ts index 347e512..7c4f472 100644 --- a/src/api/mock.ts +++ b/src/api/mock.ts @@ -85,6 +85,8 @@ export const mockApi = { setTimeout(() => resolve({ id, text: 'Добрый день!\n\nИнформируем вас о необходимости произвести оплату за 4 семестр обучения до 15 апреля 2026 года. В случае возникновения вопросов, пожалуйста, свяжитесь с бухгалтерией по внутреннему номеру 102.\n\nС уважением, Администрация.', + idinfo: '456', + item: '789', history: [ { date: '10.04.2026', author: 'Бухгалтерия', text: 'Уведомление об оплате' } ], @@ -92,6 +94,27 @@ export const mockApi = { }), 500); }); }, + getRecipients: async (): Promise<{ id: string; name: string }[]> => { + return new Promise((resolve) => { + setTimeout(() => resolve([ + { id: '1', name: 'Петров Петр Петрович' }, + { id: '2', name: 'Сидоров Сидор Сидорович' }, + { id: '3', name: 'Деканат ИСС' } + ]), 300); + }); + }, + sendMessage: async (recipientId: string, subject: string, text: string): Promise => { + return new Promise((resolve) => { + console.log('Mock Send:', { recipientId, subject, text }); + setTimeout(() => resolve({ success: true }), 1000); + }); + }, + replyToMessage: async (id: string, text: string, item?: string, idinfo?: string): Promise => { + return new Promise((resolve) => { + console.log('Mock Reply:', { id, text, item, idinfo }); + setTimeout(() => resolve({ success: true }), 1000); + }); + }, getSchedule: async (): Promise => { return new Promise((resolve) => { setTimeout(() => resolve(mockSchedule), 500); diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx index 6f884b9..7b002f7 100644 --- a/src/components/Navigation.tsx +++ b/src/components/Navigation.tsx @@ -22,7 +22,7 @@ interface NavItemConfig { label: string; icon: React.ElementType; badgeKey?: 'messages'; - children?: { path: string; label: string }[]; + children?: { path: string; label: string; exact?: boolean }[]; hideInRail?: boolean; } @@ -33,7 +33,7 @@ const navItems: NavItemConfig[] = [ label: 'Расписание', icon: CalendarDays, children: [ - { path: '/schedule', label: 'Текущая неделя' }, + { path: '/schedule', label: 'Текущая неделя', exact: true }, { path: '/schedule/session', label: 'Сессия' }, ] }, @@ -42,7 +42,7 @@ const navItems: NavItemConfig[] = [ label: 'Успеваемость', icon: GraduationCap, children: [ - { path: '/grades', label: 'Оценки' }, + { path: '/grades', label: 'Оценки', exact: true }, { path: '/debts', label: 'Задолженности' }, ] }, @@ -114,44 +114,53 @@ export const Navigation: React.FC = () => { - {isExpanded && Bonch} + {isExpanded && Bonch} - {isExpanded && profile && ( - - - - {getInitials(profile.fullName)} - - -
-
{profile.fullName}
-
{profile.group || 'БЕЗ ГРУППЫ'}
-
-
+ {profile && ( +
+ + + + {getInitials(profile.fullName)} + + + {isExpanded && ( +
+
{profile.fullName}
+
{profile.group || 'БЕЗ ГРУППЫ'}
+
+ )} +
+
)} -
+
{navItems.filter(i => !i.hideInRail).map((item) => { const hasChildren = item.children && item.children.length > 0; - // Fix #14: Sub-item logic - const isChildActive = hasChildren && item.children!.some(child => location.pathname === child.path); + // Improved active state logic for sub-items + const isChildActive = hasChildren && item.children!.some(child => + child.exact ? location.pathname === child.path : location.pathname.startsWith(child.path) + ); + const isItemExpanded = expandedItems.includes(item.label) || isChildActive; return ( -
+
{ if (hasChildren && isExpanded) { @@ -174,7 +183,7 @@ export const Navigation: React.FC = () => {
{ cn("submenu-item", isActive && "active")} > {child.label} @@ -197,10 +206,14 @@ export const Navigation: React.FC = () => { })}
-
+
cn("nav-item mx-0 h-10 px-4", isActive && "active")} + className={({ isActive }) => cn( + "nav-item mx-0 h-10 px-4", + isActive && "active", + !isExpanded && "w-10 px-0 justify-center mx-auto" + )} >
@@ -209,7 +222,10 @@ export const Navigation: React.FC = () => {
-
+ {isCreating && ( +
+ + +

Новое сообщение

+ +
+
+ + +
+ + setNewSubject(e.target.value)} + /> + +
+ +