|
|
import React, { useState, useEffect } from 'react'; |
|
|
import { createClient } from '@supabase/supabase-js'; |
|
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; |
|
|
import { faTachometerAlt, faCalendarAlt, faTools, faWrench, faClipboardCheck, faChartLine, faUsersCog, faBell, faBars } from '@fortawesome/free-solid-svg-icons'; |
|
|
import Swal from 'sweetalert2'; |
|
|
import Dashboard from './components/Dashboard'; |
|
|
import Equipment from './components/Equipment'; |
|
|
|
|
|
|
|
|
const supabase = createClient( |
|
|
import.meta.env.VITE_SUPABASE_URL, |
|
|
import.meta.env.VITE_SUPABASE_ANON_KEY |
|
|
); |
|
|
|
|
|
function App() { |
|
|
const [currentTab, setCurrentTab] = useState('dashboard'); |
|
|
const [equipment, setEquipment] = useState([]); |
|
|
const [maintenanceEvents, setMaintenanceEvents] = useState([]); |
|
|
const [isLoggedIn, setIsLoggedIn] = useState(false); |
|
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false); |
|
|
|
|
|
useEffect(() => { |
|
|
const checkUser = async () => { |
|
|
const { data: { user } } = await supabase.auth.getUser(); |
|
|
if (user) { |
|
|
setIsLoggedIn(true); |
|
|
loadData(); |
|
|
} |
|
|
}; |
|
|
checkUser(); |
|
|
}, []); |
|
|
|
|
|
const loadData = async () => { |
|
|
try { |
|
|
const [equipmentResult, eventsResult] = await Promise.all([ |
|
|
supabase.from('equipment').select('*').order('created_at', { ascending: false }), |
|
|
supabase.from('maintenance_events').select('*').order('date', { ascending: false }) |
|
|
]); |
|
|
|
|
|
if (equipmentResult.error) throw equipmentResult.error; |
|
|
if (eventsResult.error) throw eventsResult.error; |
|
|
|
|
|
setEquipment(equipmentResult.data || []); |
|
|
setMaintenanceEvents(eventsResult.data || []); |
|
|
} catch (error) { |
|
|
console.error('Error loading data:', error); |
|
|
Swal.fire({ |
|
|
title: 'Ошибка', |
|
|
text: 'Не удалось загрузить данные', |
|
|
icon: 'error' |
|
|
}); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleLogin = async (e) => { |
|
|
e.preventDefault(); |
|
|
const formData = new FormData(e.target); |
|
|
const email = formData.get('email'); |
|
|
const password = formData.get('password'); |
|
|
|
|
|
try { |
|
|
const { error } = await supabase.auth.signInWithPassword({ |
|
|
email, |
|
|
password |
|
|
}); |
|
|
|
|
|
if (error) throw error; |
|
|
|
|
|
setIsLoggedIn(true); |
|
|
loadData(); |
|
|
} catch (error) { |
|
|
Swal.fire({ |
|
|
title: 'Ошибка входа', |
|
|
text: error.message, |
|
|
icon: 'error' |
|
|
}); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleLogout = async () => { |
|
|
await supabase.auth.signOut(); |
|
|
setIsLoggedIn(false); |
|
|
}; |
|
|
|
|
|
const handleAddEquipment = async (formData) => { |
|
|
try { |
|
|
const { error } = await supabase.from('equipment').insert([{ |
|
|
name: formData.get('name'), |
|
|
type: formData.get('type'), |
|
|
serial: formData.get('serial'), |
|
|
inventory: formData.get('inventory'), |
|
|
commission_date: formData.get('commissionDate'), |
|
|
maintenance_interval: parseInt(formData.get('interval')), |
|
|
description: formData.get('description'), |
|
|
status: 'active' |
|
|
}]); |
|
|
|
|
|
if (error) throw error; |
|
|
|
|
|
loadData(); |
|
|
Swal.fire({ |
|
|
title: 'Успешно', |
|
|
text: 'Оборудование добавлено', |
|
|
icon: 'success' |
|
|
}); |
|
|
} catch (error) { |
|
|
Swal.fire({ |
|
|
title: 'Ошибка', |
|
|
text: error.message, |
|
|
icon: 'error' |
|
|
}); |
|
|
} |
|
|
}; |
|
|
|
|
|
if (!isLoggedIn) { |
|
|
return ( |
|
|
<div className="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center"> |
|
|
<div className="bg-white rounded-lg shadow-lg w-full max-w-md p-6"> |
|
|
<h3 className="text-xl font-bold mb-4">Вход в систему</h3> |
|
|
<form onSubmit={handleLogin} className="space-y-4"> |
|
|
<input |
|
|
type="email" |
|
|
name="email" |
|
|
placeholder="Email" |
|
|
required |
|
|
className="w-full px-4 py-2 border rounded-lg" |
|
|
/> |
|
|
<input |
|
|
type="password" |
|
|
name="password" |
|
|
placeholder="Пароль" |
|
|
required |
|
|
className="w-full px-4 py-2 border rounded-lg" |
|
|
/> |
|
|
<button |
|
|
type="submit" |
|
|
className="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded-lg" |
|
|
> |
|
|
Войти |
|
|
</button> |
|
|
</form> |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|
|
|
return ( |
|
|
<div className="flex min-h-screen bg-gray-100"> |
|
|
{/* Sidebar */} |
|
|
<div className={`fixed top-0 left-0 h-full bg-blue-800 text-white ${sidebarCollapsed ? 'w-16' : 'w-64'} p-4 transition-all duration-300`}> |
|
|
<div className="flex items-center justify-between mb-6"> |
|
|
{!sidebarCollapsed && <h1 className="text-xl font-bold">MES System</h1>} |
|
|
<button onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="text-white hover:text-blue-200"> |
|
|
<FontAwesomeIcon icon={faBars} /> |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<nav> |
|
|
<ul className="space-y-2"> |
|
|
{[ |
|
|
{ id: 'dashboard', icon: faTachometerAlt, label: 'Главная' }, |
|
|
{ id: 'schedule', icon: faCalendarAlt, label: 'График ТО' }, |
|
|
{ id: 'equipment', icon: faTools, label: 'Оборудование' }, |
|
|
{ id: 'maintenance', icon: faClipboardCheck, label: 'Техобслуживание' }, |
|
|
{ id: 'reports', icon: faChartLine, label: 'Отчеты' }, |
|
|
{ id: 'users', icon: faUsersCog, label: 'Пользователи' } |
|
|
].map(item => ( |
|
|
<li key={item.id}> |
|
|
<button |
|
|
onClick={() => setCurrentTab(item.id)} |
|
|
className={`w-full text-left px-4 py-2 rounded-lg hover:bg-blue-700 flex items-center ${currentTab === item.id ? 'bg-blue-700' : ''}`} |
|
|
> |
|
|
<FontAwesomeIcon icon={item.icon} className="mr-2" /> |
|
|
{!sidebarCollapsed && item.label} |
|
|
</button> |
|
|
</li> |
|
|
))} |
|
|
</ul> |
|
|
</nav> |
|
|
</div> |
|
|
|
|
|
{/* Main Content */} |
|
|
<div className={`flex-1 ${sidebarCollapsed ? 'ml-16' : 'ml-64'} p-6 transition-all duration-300`}> |
|
|
{/* Header */} |
|
|
<header className="bg-white shadow-sm p-4 rounded-lg mb-6 flex justify-between items-center"> |
|
|
<h1 className="text-2xl font-semibold"> |
|
|
{currentTab === 'dashboard' ? 'Главная панель' : |
|
|
currentTab === 'schedule' ? 'График ТО' : |
|
|
currentTab === 'equipment' ? 'Оборудование' : |
|
|
currentTab === 'maintenance' ? 'Техобслуживание' : |
|
|
currentTab === 'reports' ? 'Отчеты' : 'Пользователи'} |
|
|
</h1> |
|
|
<div className="flex space-x-4"> |
|
|
<button className="p-2 rounded-full hover:bg-gray-100 relative"> |
|
|
<FontAwesomeIcon icon={faBell} className="text-gray-600" /> |
|
|
<span className="absolute top-0 right-0 bg-red-500 text-white text-xs w-5 h-5 flex items-center justify-center rounded-full">3</span> |
|
|
</button> |
|
|
<button |
|
|
onClick={handleLogout} |
|
|
className="bg-red-500 text-white px-4 py-2 rounded-lg hover:bg-red-600" |
|
|
> |
|
|
Выход |
|
|
</button> |
|
|
</div> |
|
|
</header> |
|
|
|
|
|
{/* Content based on current tab */} |
|
|
<div className="bg-white rounded-lg shadow p-6"> |
|
|
{currentTab === 'dashboard' && ( |
|
|
<Dashboard equipment={equipment} maintenanceEvents={maintenanceEvents} /> |
|
|
)} |
|
|
{currentTab === 'equipment' && ( |
|
|
<Equipment |
|
|
equipment={equipment} |
|
|
onAdd={handleAddEquipment} |
|
|
onEdit={(item) => { |
|
|
// Handle edit |
|
|
}} |
|
|
onDelete={async (id) => { |
|
|
try { |
|
|
const { error } = await supabase |
|
|
.from('equipment') |
|
|
.delete() |
|
|
.eq('id', id); |
|
|
|
|
|
if (error) throw error; |
|
|
|
|
|
loadData(); |
|
|
Swal.fire({ |
|
|
title: 'Успешно', |
|
|
text: 'Оборудование удалено', |
|
|
icon: 'success' |
|
|
}); |
|
|
} catch (error) { |
|
|
Swal.fire({ |
|
|
title: 'Ошибка', |
|
|
text: error.message, |
|
|
icon: 'error' |
|
|
}); |
|
|
} |
|
|
}} |
|
|
/> |
|
|
)} |
|
|
{/* Add other tab components here */} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|
|
|
export default App; |