import React, { useState, useEffect, createContext, useContext } from 'react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; import { getFirestore, doc, getDoc, addDoc, setDoc, updateDoc, deleteDoc, onSnapshot, collection, query, where, getDocs } from 'firebase/firestore'; // Контекст для аутентификации и данных Firebase const AuthContext = createContext(null); // Компонент-провайдер аутентификации function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [db, setDb] = useState(null); const [auth, setAuth] = useState(null); const [userId, setUserId] = useState(null); useEffect(() => { // Инициализация Firebase const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; const firebaseConfig = typeof __firebase_config !== 'undefined' ? JSON.parse(__firebase_config) : {}; if (Object.keys(firebaseConfig).length > 0) { const app = initializeApp(firebaseConfig); const firestore = getFirestore(app); const firebaseAuth = getAuth(app); setDb(firestore); setAuth(firebaseAuth); // Слушатель изменений состояния аутентификации const unsubscribe = onAuthStateChanged(firebaseAuth, async (currentUser) => { if (currentUser) { setUser(currentUser); setUserId(currentUser.uid); } else { // Если пользователь не аутентифицирован, попробуйте войти анонимно try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(firebaseAuth, __initial_auth_token); } else { await signInAnonymously(firebaseAuth); } } catch (error) { console.error("Ошибка анонимной аутентификации:", error); } } setLoading(false); }); return () => unsubscribe(); // Отписка при размонтировании компонента } else { console.warn("Firebase config не предоставлен. Аутентификация и Firestore не будут работать."); setLoading(false); } }, []); if (loading) { return (
Загрузка...
); } return ( {children} ); } // Хук для использования контекста аутентификации function useAuth() { return useContext(AuthContext); } // Компонент для отображения сообщений (замена alert) function MessageBox({ message, onClose }) { if (!message) return null; return (

{message}

); } // Модели данных (представляют структуру листов Google Таблиц) // В реальном приложении эти данные будут загружаться из вашего бэкенда, // который взаимодействует с Google Sheets API. // Модель Проекта class Project { constructor({ id, name, manager, customer, startDate, endDate, status, budget, description, category, priority }) { this.id = id || `proj-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // Автогенерация ID this.name = name; this.manager = manager; this.customer = customer; this.startDate = startDate; this.endDate = endDate; this.status = status; this.budget = budget; this.description = description; this.category = category; this.priority = priority; } } // Модель Задачи class Task { constructor({ id, projectId, parentTaskId, name, description, responsible, executors, plannedStartDate, plannedEndDate, actualStartDate, actualEndDate, status, priority, plannedEffort, actualEffort, progress }) { this.id = id || `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // Автогенерация ID this.projectId = projectId; this.parentTaskId = parentTaskId; // Для иерархии WBS this.name = name; this.description = description; this.responsible = responsible; this.executors = executors || []; // Массив исполнителей this.plannedStartDate = plannedStartDate; this.plannedEndDate = plannedEndDate; this.actualStartDate = actualStartDate; this.actualEndDate = actualEndDate; this.status = status; this.priority = priority; this.plannedEffort = plannedEffort; // Плановые трудозатраты (человеко-часы) this.actualEffort = actualEffort; // Фактические трудозатраты (человеко-часы) this.progress = progress || 0; // Прогресс выполнения в % } } // --- Имитация API для Google Таблиц --- // В реальном приложении эти функции будут делать fetch-запросы к вашему бэкенду, // который будет использовать Google Sheets API (например, через Google Service Account). // Бэкенд будет обрабатывать аутентификацию, авторизацию и все операции с таблицами. const MOCK_PROJECTS_DATA = [ new Project({ id: 'proj-1', name: 'Разработка CRM системы', manager: 'Иванов И.И.', customer: 'ООО "Альфа"', startDate: '2024-01-15', endDate: '2024-06-30', status: 'В работе', budget: 1500000, description: 'Разработка новой CRM системы для автоматизации продаж.', category: 'ИТ', priority: 'Высокий' }), new Project({ id: 'proj-2', name: 'Внедрение ERP системы', manager: 'Петров П.П.', customer: 'ЗАО "Бета"', startDate: '2024-03-01', endDate: '2024-12-31', status: 'Планируется', budget: 2500000, description: 'Внедрение комплексной ERP системы для управления ресурсами предприятия.', category: 'Автоматизация', priority: 'Высокий' }), ]; const MOCK_TASKS_DATA = [ new Task({ id: 'task-1', projectId: 'proj-1', name: 'Сбор требований', description: 'Сбор и анализ функциональных и нефункциональных требований.', responsible: 'Сидорова А.В.', executors: ['Сидорова А.В.', 'Козлов Д.С.'], plannedStartDate: '2024-01-15', plannedEndDate: '2024-01-31', status: 'Завершено', priority: 'Высокий', plannedEffort: 80, actualEffort: 75, progress: 100 }), new Task({ id: 'task-2', projectId: 'proj-1', parentTaskId: 'task-1', // Подзадача name: 'Разработка ТЗ', description: 'Разработка технического задания на основе собранных требований.', responsible: 'Сидорова А.В.', executors: ['Сидорова А.В.'], plannedStartDate: '2024-02-01', plannedEndDate: '2024-02-15', status: 'В работе', priority: 'Высокий', plannedEffort: 60, actualEffort: 30, progress: 50 }), new Task({ id: 'task-3', projectId: 'proj-1', name: 'Проектирование архитектуры', description: 'Разработка архитектуры CRM системы.', responsible: 'Козлов Д.С.', executors: ['Козлов Д.С.'], plannedStartDate: '2024-02-01', plannedEndDate: '2024-02-29', status: 'В работе', priority: 'Высокий', plannedEffort: 120, actualEffort: 40, progress: 30 }), new Task({ id: 'task-4', projectId: 'proj-2', name: 'Анализ бизнес-процессов', description: 'Анализ текущих бизнес-процессов для внедрения ERP.', responsible: 'Новикова Е.М.', executors: ['Новикова Е.М.', 'Федоров Г.В.'], plannedStartDate: '2024-03-01', plannedEndDate: '2024-03-31', status: 'Планируется', priority: 'Высокий', plannedEffort: 100, actualEffort: 0, progress: 0 }), ]; const mockApi = { // Имитация чтения проектов из Google Таблиц (лист "Projects") getProjects: async () => { return new Promise(resolve => setTimeout(() => resolve(MOCK_PROJECTS_DATA), 500)); }, // Имитация добавления проекта в Google Таблицы (лист "Projects") addProject: async (projectData) => { const newProject = new Project(projectData); MOCK_PROJECTS_DATA.push(newProject); return new Promise(resolve => setTimeout(() => resolve(newProject), 500)); }, // Имитация чтения задач из Google Таблиц (лист "Tasks") по ProjectID getTasksByProjectId: async (projectId) => { const projectTasks = MOCK_TASKS_DATA.filter(task => task.projectId === projectId); return new Promise(resolve => setTimeout(() => resolve(projectTasks), 500)); }, // Имитация добавления задачи в Google Таблицы (лист "Tasks") addTask: async (taskData) => { const newTask = new Task(taskData); MOCK_TASKS_DATA.push(newTask); return new Promise(resolve => setTimeout(() => resolve(newTask), 500)); }, }; // --- Компоненты UI --- // Компонент "Панель управления (Дашборд)" function Dashboard() { const { userId } = useAuth(); return (

Панель управления

Добро пожаловать в интегрированное приложение для проектного офиса!

Ваш ID пользователя: {userId || 'Неизвестен'}

Активные проекты

2

Завершено задач

1

Просрочено

0

Здесь будет отображаться агрегированная информация о проектах, задачах, ресурсах и KPI. Данные будут автоматически обновляться из Google Таблиц.

); } // Компонент "Реестр проектов" function ProjectRegister({ onSelectProject }) { const [projects, setProjects] = useState([]); const [showAddForm, setShowAddForm] = useState(false); const [message, setMessage] = useState(''); useEffect(() => { // Загрузка проектов при монтировании компонента fetchProjects(); }, []); const fetchProjects = async () => { try { const data = await mockApi.getProjects(); setProjects(data); } catch (error) { console.error("Ошибка при загрузке проектов:", error); setMessage("Не удалось загрузить проекты."); } }; const handleAddProject = async (newProjectData) => { try { await mockApi.addProject(newProjectData); setMessage('Проект успешно добавлен!'); setShowAddForm(false); fetchProjects(); // Обновить список проектов } catch (error) { console.error("Ошибка при добавлении проекта:", error); setMessage("Не удалось добавить проект."); } }; return (

Реестр проектов

setMessage('')} /> {showAddForm && ( setShowAddForm(false)} /> )} {projects.length === 0 ? (

Проекты не найдены.

) : (
{projects.map((project) => ( ))}
Название проекта Руководитель Статус Бюджет Действия
{project.name} {project.manager} {project.status} {project.budget.toLocaleString()}
)}
); } // Форма для добавления/редактирования проекта function ProjectForm({ onSubmit, onCancel, initialData = {} }) { const [formData, setFormData] = useState({ name: initialData.name || '', manager: initialData.manager || '', customer: initialData.customer || '', startDate: initialData.startDate || '', endDate: initialData.endDate || '', status: initialData.status || 'Планируется', budget: initialData.budget || '', description: initialData.description || '', category: initialData.category || '', priority: initialData.priority || 'Средний', }); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); onSubmit(formData); }; return (

{initialData.id ? 'Редактировать проект' : 'Новый проект'}

); } // Компонент "Планы работ проектов (WBS)" function ProjectWorkPlans({ project }) { const [tasks, setTasks] = useState([]); const [showAddTaskForm, setShowAddTaskForm] = useState(false); const [message, setMessage] = useState(''); useEffect(() => { if (project) { fetchTasks(project.id); } else { setTasks([]); } }, [project]); const fetchTasks = async (projectId) => { try { const data = await mockApi.getTasksByProjectId(projectId); setTasks(data); } catch (error) { console.error("Ошибка при загрузке задач:", error); setMessage("Не удалось загрузить задачи."); } }; const handleAddTask = async (newTaskData) => { try { await mockApi.addTask({ ...newTaskData, projectId: project.id }); setMessage('Задача успешно добавлена!'); setShowAddTaskForm(false); fetchTasks(project.id); // Обновить список задач } catch (error) { console.error("Ошибка при добавлении задачи:", error); setMessage("Не удалось добавить задачу."); } }; if (!project) { return (

Выберите проект из "Реестра проектов", чтобы просмотреть его задачи.

); } return (

Планы работ для проекта: "{project.name}"

setMessage('')} /> {showAddTaskForm && ( setShowAddTaskForm(false)} /> )} {tasks.length === 0 ? (

Задачи для этого проекта не найдены.

) : (
{tasks.map((task) => ( ))}
Название задачи Ответственный Статус Прогресс Плановые даты
{task.name} {task.responsible} {task.status} {task.progress}% {task.plannedStartDate} - {task.plannedEndDate}
)}
); } // Форма для добавления/редактирования задачи function TaskForm({ onSubmit, onCancel, initialData = {} }) { const [formData, setFormData] = useState({ name: initialData.name || '', description: initialData.description || '', responsible: initialData.responsible || '', executors: initialData.executors ? initialData.executors.join(', ') : '', plannedStartDate: initialData.plannedStartDate || '', plannedEndDate: initialData.plannedEndDate || '', status: initialData.status || 'Планируется', priority: initialData.priority || 'Средний', plannedEffort: initialData.plannedEffort || '', }); const handleChange = (e) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; const handleSubmit = (e) => { e.preventDefault(); const dataToSubmit = { ...formData, executors: formData.executors.split(',').map(s => s.trim()).filter(s => s), // Преобразовать строку в массив plannedEffort: parseFloat(formData.plannedEffort) || 0, }; onSubmit(dataToSubmit); }; return (

{initialData.id ? 'Редактировать задачу' : 'Новая задача'}

); } // Главный компонент приложения function App() { const [activeTab, setActiveTab] = useState('dashboard'); // 'dashboard', 'projects', 'tasks' const [selectedProject, setSelectedProject] = useState(null); const handleSelectProject = (project) => { setSelectedProject(project); setActiveTab('tasks'); }; return (

Project Office App

{activeTab === 'dashboard' && } {activeTab === 'projects' && } {activeTab === 'tasks' && }
© 2024 Интегрированное приложение для проектного офиса. Все права защищены.
); } export default App;