diff --git a/.env.example b/.env.example deleted file mode 100644 index ee09a5b..0000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -VITE_FIREBASE_API_KEY=your_api_key -VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com -VITE_FIREBASE_PROJECT_ID=your_project_id -VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com -VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id -VITE_FIREBASE_APP_ID=your_app_id diff --git a/.vibe42-run.pid b/.vibe42-run.pid index e1be92e..5bf2252 100644 --- a/.vibe42-run.pid +++ b/.vibe42-run.pid @@ -1 +1 @@ -799 +1444 diff --git a/index.html b/index.html index 7181020..efa5afb 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,639 @@ - + - - - - - Адаптационная программа стажёров - - -
- - + + + + Адаптационная программа стажёров + + + + + +
+
+ 📚 Адаптация стажёров + +
+
+ +
+ +
+
+

Адаптационная программа стажёров

+

Программа профессиональной адаптации

+
+ +
+

Путь стажёра

+
+
+ 📝Анкета +
+
+
+ 👥Группы +
+
+
+ 📚Обучение +
+
+
+ ✅Экзамен +
+
+
+ +
+
+
+

🟢 Сбор анкет открыт

+

+
+ Заполнить анкету +
+
+ +
+
+ 📋 +

Анкета стажёра

+

Анкету заполняет руководитель стажёра

+
+
+ ⚖️ +

Честное деление

+

Группы формируются автоматически

+
+
+ 📱 +

Уведомление WhatsApp

+

Тренер отправит ссылку на чат

+
+
+ + +
+ + + + + + + + + + + + + + + +
+ + + diff --git a/package.json b/package.json index ad85870..ef23f31 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,4 @@ { "name": "intern-adaptation", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "start": "vite --host 0.0.0.0", - "build": "vite build", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.28.0" - }, - "devDependencies": { - "@vitejs/plugin-react": "^4.3.4", - "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", - "tailwindcss": "^3.4.15", - "vite": "^5.4.11" - } + "private": true } diff --git a/postcss.config.js b/postcss.config.js deleted file mode 100644 index 2e7af2b..0000000 --- a/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/src/App.jsx b/src/App.jsx deleted file mode 100644 index 862d12d..0000000 --- a/src/App.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Routes, Route, Navigate } from 'react-router-dom' -import { useAuth } from './context/StorageContext' -import Home from './pages/Home' -import Form from './pages/Form' -import Trainer from './pages/Trainer' -import Progress from './pages/Progress' -import Exam from './pages/Exam' -import WhatsApp from './pages/WhatsApp' -import Header from './components/Header' - -function ProtectedRoute({ children }) { - const { user } = useAuth() - return user ? children : -} - -function App() { - return ( -
-
- - } /> - } /> - - - - } /> - - - - } /> - - - - } /> - - - - } /> - -
- ) -} - -export default App diff --git a/src/components/GroupsList.jsx b/src/components/GroupsList.jsx deleted file mode 100644 index 98329f1..0000000 --- a/src/components/GroupsList.jsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useStorage } from '../context/StorageContext' - -export default function GroupsList() { - const { data } = useStorage() - const { trainees, program } = data - - if (!program.groupsFormed) { - return null - } - - const groups = {} - trainees.forEach(trainee => { - const groupId = trainee.groupId || 'Без группы' - if (!groups[groupId]) { - groups[groupId] = [] - } - groups[groupId].push(trainee) - }) - - return ( -
-

Список групп

-
- {Object.entries(groups).map(([groupId, members]) => ( -
-

- Группа {groupId} -

-
    - {members.map(trainee => ( -
  • - - {trainee.lastName} {trainee.firstName} - - {trainee.city} -
  • - ))} -
-
- ))} -
-
- ) -} diff --git a/src/components/Header.jsx b/src/components/Header.jsx deleted file mode 100644 index fea0cc2..0000000 --- a/src/components/Header.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Link, useLocation } from 'react-router-dom' -import { useAuth } from '../context/StorageContext' - -export default function Header() { - const { user, logout } = useAuth() - const location = useLocation() - - const isActive = (path) => location.pathname === path - - return ( -
-
-
- - 📚 Адаптация стажёров - - - -
-
-
- ) -} diff --git a/src/components/Hero.jsx b/src/components/Hero.jsx deleted file mode 100644 index 74ad5ff..0000000 --- a/src/components/Hero.jsx +++ /dev/null @@ -1,14 +0,0 @@ -export default function Hero({ title, description }) { - return ( -
-
-

- {title} -

-

- {description} -

-
-
- ) -} diff --git a/src/components/InfoCards.jsx b/src/components/InfoCards.jsx deleted file mode 100644 index 60f1800..0000000 --- a/src/components/InfoCards.jsx +++ /dev/null @@ -1,35 +0,0 @@ -const cards = [ - { - icon: '📋', - title: 'Анкета стажёра', - description: 'Анкету заполняет руководитель стажёра, а не сам стажёр. Укажите все контактные данные и подразделение.' - }, - { - icon: '⚖️', - title: 'Честное деление', - description: 'Группы формируются автоматически с учётом подразделения и города для равномерного распределения.' - }, - { - icon: '📱', - title: 'Уведомление в WhatsApp', - description: 'После формирования группы тренер отправит вам ссылку на чат группы в WhatsApp.' - } -] - -export default function InfoCards() { - return ( -
- {cards.map((card, index) => ( -
- {card.icon} -

- {card.title} -

-

- {card.description} -

-
- ))} -
- ) -} diff --git a/src/components/StatusCard.jsx b/src/components/StatusCard.jsx deleted file mode 100644 index 0161ee1..0000000 --- a/src/components/StatusCard.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import { useState, useEffect } from 'react' -import { Link } from 'react-router-dom' - -export default function StatusCard({ program }) { - const [daysLeft, setDaysLeft] = useState(0) - - useEffect(() => { - if (program.endDate) { - const end = new Date(program.endDate) - const now = new Date() - const diff = Math.ceil((end - now) / (1000 * 60 * 60 * 24)) - setDaysLeft(Math.max(0, diff)) - } - }, [program.endDate]) - - const isOpen = program.collectionOpen && !program.groupsFormed - - return ( -
-
-
-

- {isOpen ? '🟢 Сбор анкет открыт' : '🔴 Сбор анкет закрыт'} -

- {program.endDate && ( -

- Окончание приёма: {new Date(program.endDate).toLocaleDateString('ru-RU')} -

- )} - {isOpen && daysLeft > 0 && ( -

- Осталось дней: {daysLeft} -

- )} -
- - {isOpen && ( - - Заполнить анкету - - )} -
-
- ) -} diff --git a/src/components/TraineePath.jsx b/src/components/TraineePath.jsx deleted file mode 100644 index e696184..0000000 --- a/src/components/TraineePath.jsx +++ /dev/null @@ -1,37 +0,0 @@ -export default function TraineePath({ currentStage }) { - const stages = [ - { id: 1, name: 'Анкета', icon: '📝' }, - { id: 2, name: 'Формирование группы', icon: '👥' }, - { id: 3, name: 'Обучение и адаптация', icon: '📚' }, - { id: 4, name: 'Итоговый экзамен', icon: '✅' } - ] - - return ( -
-

Путь стажёра

-
- {stages.map((stage, index) => ( -
-
= stage.id - ? 'bg-primary-100 text-primary-700' - : 'bg-gray-100 text-gray-400' - }`} - > - {stage.icon} - {stage.name} -
- {index < stages.length - 1 && ( -
stage.id ? 'bg-primary-500' : 'bg-gray-300' - }`} - /> - )} -
- ))} -
-
- ) -} diff --git a/src/context/StorageContext.jsx b/src/context/StorageContext.jsx deleted file mode 100644 index 569de5e..0000000 --- a/src/context/StorageContext.jsx +++ /dev/null @@ -1,234 +0,0 @@ -import { createContext, useContext, useState, useEffect } from 'react' -import { auth, signInWithEmailAndPassword, signOut as firebaseSignOut, onAuthStateChanged } from '../firebase' -import { db, collection, getDocs, doc, setDoc, updateDoc } from 'firebase/firestore' - -const StorageContext = createContext(null) - -const CITIES = [ - 'Алматы', 'Астана', 'Шымкент', 'Караганда', 'Актобе', 'Тараз', 'Павлодар', - 'Усть-Каменогорск', 'Семей', 'Атырау', 'Орал', 'Кызылорда', 'Костанай', - 'Петропавловск', 'Актау', 'Темиртау', 'Другой' -] - -const DEFAULT_DATA = { - program: { - title: 'Адаптационная программа стажёров', - description: 'Программа профессиональной адаптации для новых сотрудников', - startDate: '', - endDate: '', - collectionOpen: false, - groupsFormed: false, - examEnabled: false, - numberOfGroups: 2 - }, - trainers: [ - { id: 1, name: 'Тренер 1', login: 'trainer1', password: 'pass1' }, - { id: 2, name: 'Тренер 2', login: 'trainer2', password: 'pass2' } - ], - programRules: 'Правила программы:\n1. Посещаемость обязательна\n2. Домашние задания сдавать вовремя\n3. Активно участвовать в обсуждениях', - examRules: 'Правила экзамена:\n1. 10 вопросов\n2. Порог сдачи: 9 правильных ответов (90%)\n3. Время выполнения: 60 минут', - sessions: [], - trainees: [] -} - -export function StorageProvider({ children }) { - const [user, setUser] = useState(null) - const [data, setData] = useState(DEFAULT_DATA) - const [loading, setLoading] = useState(true) - - useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, (user) => { - setUser(user) - setLoading(false) - }) - return unsubscribe - }, []) - - useEffect(() => { - loadData() - }, []) - - const loadData = async () => { - try { - const stored = localStorage.getItem('internAdaptationData') - if (stored) { - setData(JSON.parse(stored)) - } - } catch (error) { - console.error('Error loading data:', error) - } - } - - const saveData = (newData) => { - setData(newData) - localStorage.setItem('internAdaptationData', JSON.stringify(newData)) - } - - const updateProgram = (updates) => { - const newData = { ...data, program: { ...data.program, ...updates } } - saveData(newData) - } - - const updateTrainers = (trainers) => { - const newData = { ...data, trainers } - saveData(newData) - } - - const updateProgramRules = (rules) => { - const newData = { ...data, programRules: rules } - saveData(newData) - } - - const updateExamRules = (rules) => { - const newData = { ...data, examRules: rules } - saveData(newData) - } - - const addSession = (session) => { - const newSessions = [...data.sessions, { ...session, id: Date.now() }] - const newData = { ...data, sessions: newSessions } - saveData(newData) - } - - const removeSession = (sessionId) => { - const newSessions = data.sessions.filter(s => s.id !== sessionId) - const newData = { ...data, sessions: newSessions } - saveData(newData) - } - - const addTrainee = (trainee) => { - const newTrainees = [...data.trainees, { ...trainee, id: Date.now(), groupId: null, attendance: {}, homeworkStatus: {}, examScore: null }] - const newData = { ...data, trainees: newTrainees } - saveData(newData) - } - - const updateTrainee = (id, updates) => { - const newTrainees = data.trainees.map(t => t.id === id ? { ...t, ...updates } : t) - const newData = { ...data, trainees: newTrainees } - saveData(newData) - } - - const deleteTrainee = (id) => { - const newTrainees = data.trainees.filter(t => t.id !== id) - const newData = { ...data, trainees: newTrainees } - saveData(newData) - } - - const formGroups = () => { - const { numberOfGroups } = data.program - const trainees = [...data.trainees] - - // Группировка по подразделениям и городам - const groups = Array.from({ length: numberOfGroups }, () => []) - - // Сортировка для равномерного распределения - trainees.sort((a, b) => { - if (a.division !== b.division) return a.division.localeCompare(b.division) - return a.city.localeCompare(b.city) - }) - - // Распределение по группам - trainees.forEach((trainee, index) => { - groups[index % numberOfGroups].push(trainee.id) - }) - - // Обновление trainees с groupId - const updatedTrainees = data.trainees.map(t => { - const groupIndex = groups.findIndex(g => g.includes(t.id)) - return { ...t, groupId: groupIndex >= 0 ? groupIndex + 1 : null } - }) - - const newData = { - ...data, - trainees: updatedTrainees, - program: { ...data.program, groupsFormed: true, collectionOpen: false } - } - saveData(newData) - } - - const resetGroups = () => { - const updatedTrainees = data.trainees.map(t => ({ - ...t, - groupId: null, - attendance: {}, - homeworkStatus: {}, - examScore: null - })) - const newData = { - ...data, - trainees: updatedTrainees, - program: { ...data.program, groupsFormed: false, examEnabled: false } - } - saveData(newData) - } - - const login = async (login, password) => { - const trainer = data.trainers.find(t => t.login === login && t.password === password) - if (!trainer) { - throw new Error('Неверный логин или пароль') - } - // Используем демо-режим с localStorage - localStorage.setItem('demoUser', JSON.stringify(trainer)) - window.dispatchEvent(new Event('storage')) - } - - const logout = () => { - localStorage.removeItem('demoUser') - window.dispatchEvent(new Event('storage')) - } - - const value = { - user, - data, - loading, - CITIES, - updateProgram, - updateTrainers, - updateProgramRules, - updateExamRules, - addSession, - removeSession, - addTrainee, - updateTrainee, - deleteTrainee, - formGroups, - resetGroups, - login, - logout - } - - return ( - - {children} - - ) -} - -export function useStorage() { - const context = useContext(StorageContext) - if (!context) { - throw new Error('useStorage must be used within StorageProvider') - } - return context -} - -export function useAuth() { - const [demoUser, setDemoUser] = useState(null) - - useEffect(() => { - const user = localStorage.getItem('demoUser') - if (user) { - setDemoUser(JSON.parse(user)) - } - - const handler = () => { - const user = localStorage.getItem('demoUser') - setDemoUser(user ? JSON.parse(user) : null) - } - - window.addEventListener('storage', handler) - return () => window.removeEventListener('storage', handler) - }, []) - - return { user: demoUser, loading: false } -} diff --git a/src/firebase.js b/src/firebase.js deleted file mode 100644 index 25fdb87..0000000 --- a/src/firebase.js +++ /dev/null @@ -1,16 +0,0 @@ -// Демо-режим: Firebase не подключён, используется localStorage -// Для подключения Firebase добавь зависимости и настрой firebaseConfig - -export const db = null -export const auth = null - -export function collection() { return null } -export function addDoc() { return null } -export function getDocs() { return null } -export function updateDoc() { return null } -export function deleteDoc() { return null } -export function doc() { return null } -export function setDoc() { return null } -export function signInWithEmailAndPassword() { return null } -export function signOut() { return null } -export function onAuthStateChanged() { return () => {} } diff --git a/src/index.css b/src/index.css deleted file mode 100644 index cfdb981..0000000 --- a/src/index.css +++ /dev/null @@ -1,43 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; -} - -.btn-primary { - @apply bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed; -} - -.btn-secondary { - @apply bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium py-2 px-4 rounded-lg transition-colors duration-200; -} - -.input-field { - @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500 focus:border-primary-500 outline-none transition-all; -} - -.card { - @apply bg-white rounded-xl shadow-md p-6; -} - -.table-container { - @apply overflow-x-auto rounded-lg border border-gray-200; -} - -.table { - @apply min-w-full divide-y divide-gray-200; -} - -.table th { - @apply px-6 py-3 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider; -} - -.table td { - @apply px-6 py-4 whitespace-nowrap text-sm text-gray-900; -} - -.table tr:hover { - @apply bg-gray-50; -} diff --git a/src/main.jsx b/src/main.jsx deleted file mode 100644 index 885679a..0000000 --- a/src/main.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import { BrowserRouter } from 'react-router-dom' -import App from './App' -import { StorageProvider } from './context/StorageContext' -import './index.css' - -ReactDOM.createRoot(document.getElementById('root')).render( - - - - - - - -) diff --git a/src/pages/Exam.jsx b/src/pages/Exam.jsx deleted file mode 100644 index ee209c5..0000000 --- a/src/pages/Exam.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useStorage } from '../context/StorageContext' - -export default function Exam() { - const { data, updateProgram, updateExamRules, updateTrainee } = useStorage() - const { program, trainees } = data - - const passed = trainees.filter(t => t.examScore !== null && t.examScore >= 9).length - const totalWithScore = trainees.filter(t => t.examScore !== null).length - - const handleScoreChange = (traineeId, score) => { - const numScore = parseInt(score) || null - updateTrainee(traineeId, { examScore: numScore }) - } - - const getExamStatus = (score) => { - if (score === null) return { text: 'Не оценён', color: 'text-gray-500' } - if (score >= 9) return { text: 'Сдан', color: 'text-green-600' } - return { text: 'Не сдан', color: 'text-red-600' } - } - - return ( -
-

Итоговый экзамен

- -
-

Настройки экзамена

- -
- updateProgram({ examEnabled: e.target.checked })} - disabled={!program.groupsFormed} - className="w-4 h-4 text-primary-600 rounded" - /> - -
- -
- -