uchill/front_material/IMPLEMENTATION_PLAN.md

30 KiB
Raw Permalink Blame History

🚀 План реализации нового Frontend (Material UI 3 + Next.js 16)

Дата создания: 26 января 2026
Проект: Uchill Platform - Frontend Material
Цель: Создать новый frontend с Material UI 3, iOS 24+ дизайном и панелью управления снизу


📋 Общая информация

Технологический стек:

  • Framework: Next.js 16.1+ (с Turbopack)
  • UI Library: Material Web Components 3 (@material/web) - ТОЛЬКО Material компоненты!
  • Layout System: Material Design 3 Grid System
  • Styling: CSS Variables (Material Theme)
  • TypeScript: Да
  • State Management: React Context API
  • API Client: Axios
  • Real-time: WebSocket (чат, доска), WebRTC (видеозвонки)
  • Icons: Material Symbols (Google Fonts)

Дизайн-система:

  • Цвета: Из landing_site (см. ниже)
  • Стиль: iOS 24+ (rounded corners, blur effects, glassmorphism)
  • Навигация: Bottom Navigation Bar (iOS-style, Material Navigation)
  • Компоненты: ТОЛЬКО Material Web Components 3 (@material/web)
  • Layout: Material Design 3 Grid System
  • Styling: Чистый CSS с CSS Variables (БЕЗ Tailwind CSS)

🎨 Цветовая палитра из landing_site

:root {
  --theme: #7444FD;           /* Основной фиолетовый */
  --theme2: #F9F3EF;          /* Бежевый фон */
  --theme3: #FAF8FF;          /* Светло-фиолетовый фон */
  --title: #282C32;            /* Темно-серый для заголовков */
  --text: #858585;             /* Серый для текста */
  --text2: #cbcbcb;            /* Светло-серый */
  --border: #E6E6E6;           /* Границы */
  --border-2: #F1F1F1;         /* Светлые границы */
  --bg-1: #161921;             /* Темный фон */
  --bg-2: #F6F7FF;            /* Светлый фон */
  --white: #fff;
  --black: #000;
  --orange: #e78c45;
}

Адаптация для iOS 24+ стиля:

  • Использовать blur effects (backdrop-filter)
  • Rounded corners (16px, 20px, 24px)
  • Glassmorphism для панелей
  • Soft shadows
  • Smooth animations

📦 Этап 1: Подготовка проекта и инфраструктуры

Задача 1.1: Инициализация Next.js 16 проекта

  • Создать новый Next.js проект в front_material/
  • Настроить TypeScript конфигурацию
  • Настроить ESLint и Prettier
  • Настроить пути импортов (@/components, @/utils, и т.д.)
  • Создать базовую структуру папок

Команды:

cd front_material
npx create-next-app@latest . --typescript --no-tailwind --app --no-src-dir
npm install @material/web
npm install axios date-fns livekit-client
npm install -D @types/node @types/react @types/react-dom

⚠️ Важно: Создаем проект БЕЗ Tailwind CSS (--no-tailwind)!

Задача 1.2: Настройка Docker

  • Создать Dockerfile для Next.js 16
  • Создать .dockerignore
  • Настроить multi-stage build (development, production)
  • Обновить docker-compose.yml (отключить старый frontend, добавить новый)
  • Настроить hot reload в Docker

Dockerfile структура:

# Development stage
FROM node:20-alpine AS development
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]

# Production stage
FROM node:20-alpine AS production
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

Задача 1.3: Настройка переменных окружения

  • Создать .env.example
  • Настроить NEXT_PUBLIC_API_URL
  • Настроить NEXT_PUBLIC_WS_URL
  • Настроить другие переменные окружения

Файл .env.example:

NEXT_PUBLIC_API_URL=http://localhost:8123/api
NEXT_PUBLIC_WS_URL=ws://localhost:8123/ws
NEXT_PUBLIC_LIVEKIT_URL=ws://localhost:7880
NODE_ENV=development

Задача 1.4: Настройка Material Web Components

  • Установить @material/web
  • Настроить импорт компонентов
  • Создать wrapper компоненты для React
  • Настроить темизацию (цвета из landing_site)
  • Создать базовые стили

Структура:

front_material/
├── src/
│   ├── components/
│   │   ├── material/          # Wrapper компоненты для Material UI
│   │   │   ├── Button.tsx
│   │   │   ├── TextField.tsx
│   │   │   ├── Card.tsx
│   │   │   └── ...
│   │   └── ...
│   ├── styles/
│   │   ├── material-theme.css  # Кастомная тема Material UI
│   │   └── globals.css        # Глобальные стили

🎨 Этап 2: Дизайн-система и базовые компоненты

Задача 2.1: Создание цветовой темы

  • Создать CSS переменные на основе цветов landing_site
  • Адаптировать цвета для iOS 24+ стиля
  • Создать темную тему (dark mode)
  • Настроить Material UI тему с кастомными цветами

Файл src/styles/theme.css:

:root {
  /* Основные цвета из landing_site */
  --md-sys-color-primary: #7444FD;
  --md-sys-color-on-primary: #FFFFFF;
  --md-sys-color-primary-container: #FAF8FF;
  --md-sys-color-on-primary-container: #282C32;
  
  /* iOS 24+ адаптация */
  --ios-blur-background: rgba(255, 255, 255, 0.8);
  --ios-blur-background-dark: rgba(0, 0, 0, 0.6);
  --ios-border-radius: 20px;
  --ios-border-radius-small: 16px;
  --ios-border-radius-large: 24px;
  --ios-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] {
  --md-sys-color-primary: #9D6AFF;
  --ios-blur-background: rgba(0, 0, 0, 0.8);
}

Задача 2.2: Использование Material Web Components (напрямую)

  • Импортировать все нужные Material компоненты
  • Настроить типизацию для Web Components в TypeScript
  • Создать глобальный импорт всех Material компонентов

Важно: Используем Material Web Components напрямую в JSX/TSX, без wrapper компонентов!

Файл src/lib/material-components.ts:

// Импортируем все нужные Material компоненты
import '@material/web/button/filled-button.js';
import '@material/web/button/outlined-button.js';
import '@material/web/button/text-button.js';
import '@material/web/button/elevated-button.js';
import '@material/web/button/tonal-button.js';

import '@material/web/textfield/filled-text-field.js';
import '@material/web/textfield/outlined-text-field.js';

import '@material/web/card/filled-card.js';
import '@material/web/card/elevated-card.js';
import '@material/web/card/outlined-card.js';

import '@material/web/list/list.js';
import '@material/web/list/list-item.js';

import '@material/web/dialog/dialog.js';
import '@material/web/chip/chip-set.js';
import '@material/web/chip/assist-chip.js';
import '@material/web/chip/filter-chip.js';

import '@material/web/icon/icon.js';
import '@material/web/iconbutton/icon-button.js';

import '@material/web/checkbox/checkbox.js';
import '@material/web/radio/radio.js';
import '@material/web/switch/switch.js';
import '@material/web/select/filled-select.js';
import '@material/web/select/outlined-select.js';

// И другие по необходимости

Использование в компонентах:

'use client';

export default function LoginPage() {
  return (
    <div>
      <md-outlined-text-field 
        label="Email"
        type="email"
      ></md-outlined-text-field>
      
      <md-filled-button>Войти</md-filled-button>
    </div>
  );
}

Задача 2.3: Настройка TypeScript для Web Components

  • Создать типы для Material Web Components
  • Настроить JSX для использования Web Components

Файл src/types/material-web.d.ts:

declare namespace JSX {
  interface IntrinsicElements {
    'md-filled-button': any;
    'md-outlined-button': any;
    'md-text-button': any;
    'md-elevated-button': any;
    'md-tonal-button': any;
    'md-filled-text-field': any;
    'md-outlined-text-field': any;
    'md-filled-card': any;
    'md-elevated-card': any;
    'md-outlined-card': any;
    'md-list': any;
    'md-list-item': any;
    'md-dialog': any;
    'md-chip-set': any;
    'md-assist-chip': any;
    'md-filter-chip': any;
    'md-icon': any;
    'md-icon-button': any;
    'md-checkbox': any;
    'md-radio': any;
    'md-switch': any;
    'md-filled-select': any;
    'md-outlined-select': any;
    // ... другие компоненты
  }
}

Задача 2.4: Настройка Material Grid System

  • Использовать CSS Grid из Material Design 3
  • Настроить responsive breakpoints
  • Создать Layout Grid компоненты

Material Design 3 Grid:

/* src/styles/material-grid.css */

/* Material Design 3 Grid System */
.md-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 16px;
  padding: 0 16px;
}

/* Responsive breakpoints (Material Design 3) */
@media (min-width: 600px) {  /* Tablet */
  .md-grid {
    gap: 24px;
    padding: 0 24px;
  }
}

@media (min-width: 840px) {  /* Desktop */
  .md-grid {
    gap: 24px;
    padding: 0 24px;
  }
}

@media (min-width: 1240px) { /* Large Desktop */
  .md-grid {
    gap: 24px;
    max-width: 1200px;
    margin: 0 auto;
  }
}

/* Grid column classes */
.md-col-1 { grid-column: span 1; }
.md-col-2 { grid-column: span 2; }
.md-col-3 { grid-column: span 3; }
.md-col-4 { grid-column: span 4; }
.md-col-6 { grid-column: span 6; }
.md-col-8 { grid-column: span 8; }
.md-col-12 { grid-column: span 12; }

/* Responsive columns */
@media (max-width: 599px) {
  .md-col-sm-12 { grid-column: span 12; }
}

@media (min-width: 600px) and (max-width: 839px) {
  .md-col-md-6 { grid-column: span 6; }
  .md-col-md-12 { grid-column: span 12; }
}

@media (min-width: 840px) {
  .md-col-lg-4 { grid-column: span 4; }
  .md-col-lg-6 { grid-column: span 6; }
  .md-col-lg-8 { grid-column: span 8; }
}

Задача 2.5: Создание Bottom Navigation Bar (iOS-style + Material)

  • Компонент BottomNavigationBar с использованием Material компонентов
  • Использовать md-navigation-bar и md-navigation-tab
  • Кастомизация под iOS 24+ стиль
  • Badge для уведомлений (Material Badge)
  • Адаптация под разные роли (mentor, client, parent)

Использование Material Navigation:

'use client';

import '@material/web/labs/navigationbar/navigation-bar.js';
import '@material/web/labs/navigationtab/navigation-tab.js';
import '@material/web/icon/icon.js';
import '@material/web/badge/badge.js';

export function BottomNavigationBar({ userRole }: { userRole: string }) {
  return (
    <md-navigation-bar className="ios-bottom-bar">
      <md-navigation-tab label="Главная" active>
        <md-icon slot="inactive-icon">home</md-icon>
        <md-icon slot="active-icon">home</md-icon>
      </md-navigation-tab>
      
      <md-navigation-tab label="Расписание">
        <md-icon slot="inactive-icon">calendar_month</md-icon>
        <md-icon slot="active-icon">calendar_month</md-icon>
      </md-navigation-tab>
      
      <md-navigation-tab label="Чат">
        <md-icon slot="inactive-icon">chat</md-icon>
        <md-icon slot="active-icon">chat</md-icon>
        <md-badge value="3"></md-badge>
      </md-navigation-tab>
      
      {userRole === 'mentor' && (
        <md-navigation-tab label="Студенты">
          <md-icon slot="inactive-icon">group</md-icon>
          <md-icon slot="active-icon">group</md-icon>
        </md-navigation-tab>
      )}
    </md-navigation-bar>
  );
}

Кастомизация под iOS стиль:

/* src/styles/ios-navigation.css */
md-navigation-bar.ios-bottom-bar {
  --md-navigation-bar-container-color: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-top: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.05);
  border-radius: 24px 24px 0 0;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1000;
}

[data-theme="dark"] md-navigation-bar.ios-bottom-bar {
  --md-navigation-bar-container-color: rgba(0, 0, 0, 0.8);
  border-top-color: rgba(255, 255, 255, 0.1);
}

🏗️ Этап 3: Архитектура и структура приложения

Задача 3.1: Настройка роутинга Next.js App Router

  • Создать структуру папок для маршрутов
  • Настроить Route Groups: (auth), (protected)
  • Создать layout для защищенных страниц
  • Создать layout для публичных страниц
  • Настроить middleware для проверки авторизации

Структура:

src/app/
├── (auth)/
│   ├── login/
│   ├── register/
│   └── layout.tsx
├── (protected)/
│   ├── dashboard/
│   ├── schedule/
│   ├── chat/
│   └── layout.tsx
├── layout.tsx
└── page.tsx

Задача 3.2: Создание системы аутентификации

  • Context для хранения пользователя (AuthContext)
  • Хуки для работы с API (useAuth, useLogin, useLogout)
  • Хранение токенов (localStorage)
  • Автоматическое обновление токенов
  • Защита маршрутов через middleware

Файл src/contexts/AuthContext.tsx:

'use client';

import { createContext, useContext, useState, useEffect } from 'react';
import { getCurrentUser } from '@/api/auth';

interface User {
  id: number;
  email: string;
  role: 'mentor' | 'client' | 'parent';
  // ...
}

interface AuthContextType {
  user: User | null;
  loading: boolean;
  login: (token: string) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextType | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  // Загрузка пользователя при монтировании
  useEffect(() => {
    loadUser();
  }, []);

  const loadUser = async () => {
    try {
      const token = localStorage.getItem('access_token');
      if (token) {
        const userData = await getCurrentUser();
        setUser(userData);
      }
    } catch (error) {
      localStorage.removeItem('access_token');
    } finally {
      setLoading(false);
    }
  };

  const login = async (token: string) => {
    localStorage.setItem('access_token', token);
    await loadUser();
  };

  const logout = () => {
    localStorage.removeItem('access_token');
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error('useAuth must be used within AuthProvider');
  return context;
}

Задача 3.3: Создание API клиента

  • Настроить Axios с базовым URL
  • Добавить interceptors для токенов
  • Обработка ошибок
  • Типизация API ответов
  • Создать модули API (auth, schedule, chat, и т.д.)

Файл src/lib/api-client.ts:

import axios from 'axios';

const apiClient = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8123/api',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Добавление токена к запросам
apiClient.interceptors.request.use((config) => {
  const token = localStorage.getItem('access_token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

// Обработка ошибок
apiClient.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      localStorage.removeItem('access_token');
      window.location.href = '/login';
    }
    return Promise.reject(error);
  }
);

export default apiClient;

Задача 3.4: Создание системы управления состоянием

  • Настроить React Context для глобального состояния
  • Опционально: добавить Zustand для сложного состояния
  • Создать stores (userStore, notificationStore, и т.д.)

📱 Этап 4: Основные страницы и компоненты

Задача 4.1: Страницы аутентификации

  • /login - страница входа
    • Material UI TextField для email/password
    • Material UI Button
    • iOS-стиль дизайн
    • Валидация форм
  • /register - страница регистрации
    • Форма с выбором роли
    • Material UI компоненты
    • Валидация
  • /forgot-password - восстановление пароля
  • /verify-email - подтверждение email

Задача 4.2: Главный Layout (защищенные страницы)

  • Создать ProtectedLayout
  • Верхняя панель навигации (iOS-style)
  • Нижняя панель навигации (Bottom Navigation Bar)
  • Контентная область с padding
  • Обработка разных ролей

Компонент ProtectedLayout.tsx:

'use client';

import { useAuth } from '@/contexts/AuthContext';
import { BottomNavigationBar } from '@/components/navigation/BottomNavigationBar';
import { TopNavigationBar } from '@/components/navigation/TopNavigationBar';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

export default function ProtectedLayout({ children }: { children: React.ReactNode }) {
  const { user, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (!loading && !user) {
      router.push('/login');
    }
  }, [user, loading, router]);

  if (loading) {
    return <LoadingScreen />;
  }

  if (!user) {
    return null;
  }

  return (
    <div className="flex flex-col h-screen bg-gray-50 dark:bg-gray-900">
      <TopNavigationBar user={user} />
      <main className="flex-1 overflow-y-auto pb-20">
        {children}
      </main>
      <BottomNavigationBar user={user} />
    </div>
  );
}

Задача 4.3: Дашборды для каждой роли

  • /dashboard/mentor - дашборд ментора
    • Статистика студентов
    • Ближайшие занятия
    • Графики доходов
    • Material UI Cards
  • /dashboard/client - дашборд клиента
    • Календарь занятий
    • Прогресс обучения
    • Статистика
  • /dashboard/parent - дашборд родителя
    • Выбор ребенка
    • Статистика детей

Задача 4.4: Страница расписания

  • Календарь (Material UI или кастомный)
  • Создание занятия
  • Редактирование занятия
  • Список занятий
  • Фильтры

Задача 4.5: Страница чата

  • Список чатов (Material UI List)
  • Окно чата
  • WebSocket интеграция
  • Отправка сообщений
  • Файлы и медиа

Задача 4.6: Страница видеозвонков

  • Интеграция с LiveKit
  • Видео компоненты
  • Управление микрофоном/камерой
  • Интерактивная доска
  • Чат во время звонка

🎨 Этап 5: Стилизация и анимации

Задача 5.1: Настройка стилей (только CSS, без Tailwind)

  • Создать CSS переменные для всей платформы
  • Использовать Material Design 3 Grid System
  • Настроить iOS 24+ эффекты (blur, rounded corners)
  • Настроить dark mode через CSS

⚠️ Важно: НЕ используем Tailwind CSS! Только чистый CSS и CSS Variables.

Файл src/styles/globals.css:

@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200');

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;
  color: var(--md-sys-color-on-surface);
  background-color: var(--md-sys-color-surface);
}

/* iOS 24+ эффекты */
.ios-blur {
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}

.ios-card {
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(20px);
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

[data-theme="dark"] .ios-card {
  background: rgba(0, 0, 0, 0.6);
}

Задача 5.2: Создание анимаций

  • Переходы между страницами
  • Анимация Bottom Navigation Bar
  • Анимация модальных окон
  • Skeleton loaders
  • Pull-to-refresh (опционально)

Задача 5.3: Адаптивный дизайн

  • Mobile-first подход
  • Breakpoints для планшетов и десктопов
  • Адаптация Bottom Navigation Bar для разных экранов
  • Оптимизация для touch устройств

🔌 Этап 6: Интеграция с Backend

Задача 6.1: Интеграция API

  • Модуль src/api/auth.ts - аутентификация
  • Модуль src/api/schedule.ts - расписание
  • Модуль src/api/chat.ts - чат
  • Модуль src/api/materials.ts - материалы
  • Модуль src/api/homework.ts - домашние задания
  • Модуль src/api/students.ts - студенты
  • Модуль src/api/payment.ts - оплата
  • И другие модули по необходимости

Задача 6.2: WebSocket интеграция

  • Хук useChatWebSocket для чата
  • Хук useBoardWebSocket для доски
  • Хук useVideoWebSocket для видеозвонков
  • Обработка переподключений
  • Обработка ошибок

Задача 6.3: Интеграция видеозвонков

  • LiveKit клиент
  • Компоненты видео
  • Управление медиа
  • Screen sharing
  • Интерактивная доска во время звонка

🐳 Этап 7: Docker и деплой

Задача 7.1: Обновление docker-compose.yml

  • Отключить старый frontend сервис
  • Добавить новый front_material сервис
  • Настроить порты
  • Настроить volumes
  • Настроить environment variables

Изменения в docker-compose.yml:

# Старый frontend - закомментировать или удалить
# frontend:
#   ...

# Новый frontend
front_material:
  build:
    context: ./front_material
    dockerfile: Dockerfile
    target: development
  container_name: platform_front_material
  restart: unless-stopped
  command: npm run dev
  environment:
    - NODE_ENV=development
    - NEXT_PUBLIC_API_URL=http://web:8000/api
    - NEXT_PUBLIC_WS_URL=ws://web:8000/ws
  ports:
    - "3000:3000"
  volumes:
    - ./front_material:/app
    - /app/node_modules
    - /app/.next
  networks:
    - app_network
  depends_on:
    - web

Задача 7.2: Production сборка

  • Настроить production Dockerfile
  • Оптимизация bundle size
  • Настройка кеширования
  • Настройка статических файлов

📝 Этап 8: Документация и тестирование

Задача 8.1: Документация

  • README.md с инструкциями по установке
  • Документация компонентов
  • Документация API интеграции
  • Документация стилей и темизации

Задача 8.2: Тестирование

  • Unit тесты для утилит
  • Integration тесты для API
  • E2E тесты для критичных flow
  • Тестирование на разных устройствах

Чеклист миграции со старого frontend

Подготовка:

  • Создать backup старого frontend
  • Экспортировать важные данные/конфигурации
  • Документировать текущие API endpoints

Миграция:

  • Постепенно переносить функциональность
  • Тестировать каждую страницу
  • Проверять интеграцию с backend
  • Проверять WebSocket соединения

Завершение:

  • Отключить старый frontend в docker-compose.yml
  • Обновить документацию
  • Обновить CI/CD (если есть)
  • Уведомить команду о изменениях

🎯 Приоритеты реализации

Фаза 1 (MVP - 2-3 недели):

  1. Этап 1: Подготовка проекта
  2. Этап 2: Базовые компоненты
  3. Этап 3: Архитектура
  4. Этап 4.1-4.3: Аутентификация и дашборды

Фаза 2 (Основной функционал - 3-4 недели):

  1. Этап 4.4-4.6: Основные страницы
  2. Этап 5: Стилизация
  3. Этап 6: Интеграция с Backend

Фаза 3 (Полировка - 1-2 недели):

  1. Этап 7: Docker и деплой
  2. Этап 8: Документация и тестирование

📚 Полезные ресурсы

Material Web Components:

Next.js 16:

iOS 24+ Design Guidelines:


⚠️ Важные замечания

  1. Vite vs Turbopack:

    • Next.js 16 уже использует Turbopack, который быстрее Vite
    • Рекомендуется использовать Turbopack (встроен в Next.js)
    • Если нужен чистый Vite, можно использовать Vite + React, но потеряете SSR
  2. Material UI 3 Web Components:

    • Это Web Components, не React компоненты
    • Нужны wrapper компоненты для использования в React
    • Альтернатива: использовать React версию Material UI, но она не Material 3
  3. Bottom Navigation:

    • На десктопе можно показывать sidebar вместо bottom bar
    • Адаптация под разные экраны обязательна
  4. Цвета:

    • Использовать цвета из landing_site как основу
    • Адаптировать под iOS 24+ стиль (blur, rounded corners)

Дата начала: _______________
Ожидаемая дата завершения: _______________
Ответственный: _______________