Merge pull request 'glistik v popke' (#2) from glist into main
Reviewed-on: https://maktraher.ru/enemy01/jopa/pulls/2
This commit is contained in:
commit
2e3ca0b579
31
src/App.js
31
src/App.js
@ -1,23 +1,24 @@
|
||||
import React from "react";
|
||||
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
|
||||
import React, { useState } from 'react';
|
||||
import DeviceGroups from "./pages/DeviceGroups";
|
||||
import Devices from "./pages/Devices";
|
||||
import Sidebar from "./Sidebar";
|
||||
import Dashboard from "./pages/Dashboard";
|
||||
import DevicesPage from "./pages/DevicesPage"; // Добавляем страницу для управления устройствами
|
||||
import Dashboard from './pages/Dashboard';
|
||||
|
||||
const App = () => {
|
||||
const [activeTab, setActiveTab] = useState('map'); // По умолчанию активная вкладка "Карта"
|
||||
|
||||
return (
|
||||
<Router>
|
||||
<div style={{ display: "flex", height: "100vh" }}>
|
||||
<Sidebar />
|
||||
<div style={{ flex: 1, padding: "20px" }}>
|
||||
<Routes>
|
||||
<Route path="/" element={<Dashboard />} /> {/* Главная страница с картой */}
|
||||
<Route path="/devices" element={<DevicesPage />} /> {/* Страница управления устройствами */}
|
||||
</Routes>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<Sidebar onSelectTab={setActiveTab} activeTab={activeTab} /> {/* Передаем активную вкладку */}
|
||||
<div style={{ padding: '20px', flex: 1 }}>
|
||||
{activeTab === 'map' && <Dashboard />} {/* Подключаем компонент Dashboard */}
|
||||
{activeTab === 'connection' && <div>Подключение</div>}
|
||||
{activeTab === 'account' && <div>Аккаунт пользователя</div>}
|
||||
{activeTab === 'groups' && <DeviceGroups />} {/* Группы устройств */}
|
||||
{activeTab === 'devices' && <Devices />} {/* Устройства */}
|
||||
</div>
|
||||
</Router>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
48
src/DeviceGroups.css
Normal file
48
src/DeviceGroups.css
Normal file
@ -0,0 +1,48 @@
|
||||
.device-groups {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.device-groups h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-creation {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.group-creation input {
|
||||
padding: 5px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.group-creation button {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #e8f5e9;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background-color: #ffebee;
|
||||
}
|
||||
|
||||
li div {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
li button {
|
||||
margin-right: 10px;
|
||||
}
|
38
src/Devices.css
Normal file
38
src/Devices.css
Normal file
@ -0,0 +1,38 @@
|
||||
.devices {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.devices h2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.devices table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.devices table, .devices th, .devices td {
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.devices th, .devices td {
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.devices th {
|
||||
background-color: #34495e;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.devices tr:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.buttons button {
|
||||
margin-right: 10px;
|
||||
}
|
@ -1,29 +1,50 @@
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar li {
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar li:hover {
|
||||
background-color: #34495e;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
width: 250px;
|
||||
height: 100vh;
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.sidebar h2 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar li {
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar li:hover {
|
||||
background-color: #34495e;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar li {
|
||||
margin: 15px 0;
|
||||
cursor: pointer;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.sidebar li:hover {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
.sidebar li.active {
|
||||
background-color: #2980b9;
|
||||
color: white;
|
||||
}
|
@ -1,58 +1,29 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import React from 'react';
|
||||
import './Sidebar.css';
|
||||
|
||||
const Sidebar = () => {
|
||||
const Sidebar = ({ onSelectTab, activeTab }) => {
|
||||
return (
|
||||
<div style={styles.sidebar}>
|
||||
<h2 style={styles.heading}>Навигация</h2>
|
||||
<ul style={styles.list}>
|
||||
<li style={styles.listItem}>
|
||||
<Link to="/" style={styles.link}>Карта всех устройств</Link>
|
||||
<div className="sidebar">
|
||||
<h2>Меню</h2>
|
||||
<ul>
|
||||
<li className={activeTab === 'map' ? 'active' : ''} onClick={() => onSelectTab('map')}>
|
||||
Карта в реальном времени
|
||||
</li>
|
||||
<li style={styles.listItem}>
|
||||
<Link to="/devices" style={styles.link}>Устройства</Link>
|
||||
<li className={activeTab === 'connection' ? 'active' : ''} onClick={() => onSelectTab('connection')}>
|
||||
Подключение
|
||||
</li>
|
||||
<li style={styles.listItem}>
|
||||
<Link to="#" style={styles.link}>Группы устройств</Link>
|
||||
<li className={activeTab === 'account' ? 'active' : ''} onClick={() => onSelectTab('account')}>
|
||||
Аккаунт пользователя
|
||||
</li>
|
||||
<li className={activeTab === 'groups' ? 'active' : ''} onClick={() => onSelectTab('groups')}>
|
||||
Группы устройств
|
||||
</li>
|
||||
<li className={activeTab === 'devices' ? 'active' : ''} onClick={() => onSelectTab('devices')}>
|
||||
Устройства
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Объект с инлайн-стилями
|
||||
const styles = {
|
||||
sidebar: {
|
||||
width: '250px',
|
||||
backgroundColor: '#2c3e50',
|
||||
padding: '20px',
|
||||
height: '100vh',
|
||||
color: 'white',
|
||||
boxShadow: '2px 0 5px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
heading: {
|
||||
fontSize: '24px',
|
||||
marginBottom: '20px',
|
||||
color: '#ecf0f1',
|
||||
borderBottom: '2px solid #34495e',
|
||||
paddingBottom: '10px',
|
||||
},
|
||||
list: {
|
||||
listStyleType: 'none',
|
||||
padding: '0',
|
||||
},
|
||||
listItem: {
|
||||
marginBottom: '15px',
|
||||
},
|
||||
link: {
|
||||
color: '#ecf0f1',
|
||||
textDecoration: 'none',
|
||||
fontSize: '18px',
|
||||
display: 'block',
|
||||
padding: '10px',
|
||||
borderRadius: '4px',
|
||||
transition: 'background-color 0.3s',
|
||||
},
|
||||
};
|
||||
|
||||
export default Sidebar;
|
87
src/pages/DeviceGroups.js
Normal file
87
src/pages/DeviceGroups.js
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { useState } from 'react';
|
||||
import '../DeviceGroups.css';
|
||||
|
||||
const DeviceGroups = () => {
|
||||
const [groups, setGroups] = useState([]);
|
||||
const [newGroupName, setNewGroupName] = useState('');
|
||||
|
||||
const createGroup = () => {
|
||||
if (newGroupName) {
|
||||
const newGroup = {
|
||||
name: newGroupName,
|
||||
devices: [],
|
||||
active: true,
|
||||
};
|
||||
setGroups([...groups, newGroup]);
|
||||
setNewGroupName('');
|
||||
}
|
||||
};
|
||||
|
||||
const deleteGroup = (groupName) => {
|
||||
setGroups(groups.filter(group => group.name !== groupName));
|
||||
};
|
||||
|
||||
const toggleGroupStatus = (groupName) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, active: !group.active } : group
|
||||
));
|
||||
};
|
||||
|
||||
const addDeviceToGroup = (groupName, device) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, devices: [...group.devices, device] } : group
|
||||
));
|
||||
};
|
||||
|
||||
const removeDeviceFromGroup = (groupName, device) => {
|
||||
setGroups(groups.map(group =>
|
||||
group.name === groupName ? { ...group, devices: group.devices.filter(d => d !== device) } : group
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="device-groups">
|
||||
<h2>Группы устройств</h2>
|
||||
|
||||
<div className="group-creation">
|
||||
<input
|
||||
type="text"
|
||||
value={newGroupName}
|
||||
onChange={(e) => setNewGroupName(e.target.value)}
|
||||
placeholder="Название новой группы"
|
||||
/>
|
||||
<button onClick={createGroup}>Создать группу</button>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{groups.map(group => (
|
||||
<li key={group.name} className={group.active ? 'active' : 'inactive'}>
|
||||
<div>
|
||||
<span>{group.name}</span>
|
||||
<button onClick={() => deleteGroup(group.name)}>Удалить</button>
|
||||
<button onClick={() => toggleGroupStatus(group.name)}>
|
||||
{group.active ? 'Деактивировать' : 'Активировать'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4>Устройства в группе:</h4>
|
||||
<ul>
|
||||
{group.devices.map((device, index) => (
|
||||
<li key={index}>
|
||||
{device} <button onClick={() => removeDeviceFromGroup(group.name, device)}>Удалить</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<button onClick={() => addDeviceToGroup(group.name, 'Новое устройство')}>
|
||||
Добавить устройство
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeviceGroups;
|
93
src/pages/Devices.js
Normal file
93
src/pages/Devices.js
Normal file
@ -0,0 +1,93 @@
|
||||
import React, { useState } from 'react';
|
||||
import { QRCodeCanvas } from "qrcode.react";
|
||||
import '../Devices.css';
|
||||
|
||||
const Devices = () => {
|
||||
const [devices, setDevices] = useState([]);
|
||||
const [selectedDevice, setSelectedDevice] = useState(null);
|
||||
const [deviceName, setDeviceName] = useState('');
|
||||
const [deviceStatus, setDeviceStatus] = useState('');
|
||||
|
||||
const addDevice = () => {
|
||||
if (deviceName) {
|
||||
const newDevice = { name: deviceName, status: deviceStatus || 'Неактивен', id: Date.now() };
|
||||
setDevices([...devices, newDevice]);
|
||||
setDeviceName('');
|
||||
setDeviceStatus('');
|
||||
}
|
||||
};
|
||||
|
||||
const removeDevice = () => {
|
||||
if (selectedDevice) {
|
||||
setDevices(devices.filter(device => device.id !== selectedDevice.id));
|
||||
setSelectedDevice(null);
|
||||
}
|
||||
};
|
||||
|
||||
const editDevice = () => {
|
||||
if (selectedDevice) {
|
||||
const updatedDevices = devices.map(device =>
|
||||
device.id === selectedDevice.id
|
||||
? { ...device, name: deviceName, status: deviceStatus }
|
||||
: device
|
||||
);
|
||||
setDevices(updatedDevices);
|
||||
setSelectedDevice(null);
|
||||
setDeviceName('');
|
||||
setDeviceStatus('');
|
||||
}
|
||||
};
|
||||
|
||||
const generateQR = (device) => {
|
||||
return (
|
||||
<QRCodeCanvas value={JSON.stringify(device)} size={128} />
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="devices">
|
||||
<h2>Устройства</h2>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Название устройства"
|
||||
value={deviceName}
|
||||
onChange={(e) => setDeviceName(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Статус устройства"
|
||||
value={deviceStatus}
|
||||
onChange={(e) => setDeviceStatus(e.target.value)}
|
||||
/>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Название</th>
|
||||
<th>Статус</th>
|
||||
<th>QR Код</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{devices.map((device) => (
|
||||
<tr key={device.id} onClick={() => setSelectedDevice(device)}>
|
||||
<td>{device.name}</td>
|
||||
<td>{device.status}</td>
|
||||
<td>{generateQR(device)}</td>
|
||||
<td>
|
||||
<button onClick={editDevice}>Редактировать</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="buttons">
|
||||
<button onClick={addDevice}>Добавить</button>
|
||||
<button onClick={removeDevice} disabled={!selectedDevice}>Удалить выбранное</button>
|
||||
<button onClick={editDevice} disabled={!selectedDevice}>Сохранить изменения</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Devices;
|
@ -1,180 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { QRCodeCanvas } from "qrcode.react";
|
||||
|
||||
const DevicesPage = () => {
|
||||
const [devices, setDevices] = useState([]);
|
||||
const [newDeviceName, setNewDeviceName] = useState("");
|
||||
const [editDeviceId, setEditDeviceId] = useState(null);
|
||||
const [editDeviceName, setEditDeviceName] = useState("");
|
||||
|
||||
const addDevice = () => {
|
||||
if (newDeviceName) {
|
||||
setDevices([...devices, { name: newDeviceName, id: Date.now() }]);
|
||||
setNewDeviceName("");
|
||||
}
|
||||
};
|
||||
|
||||
const removeDevice = (id) => {
|
||||
setDevices(devices.filter((device) => device.id !== id));
|
||||
};
|
||||
|
||||
const startEditDevice = (device) => {
|
||||
setEditDeviceId(device.id);
|
||||
setEditDeviceName(device.name);
|
||||
};
|
||||
|
||||
const saveDeviceEdit = () => {
|
||||
setDevices(
|
||||
devices.map((device) =>
|
||||
device.id === editDeviceId ? { ...device, name: editDeviceName } : device
|
||||
)
|
||||
);
|
||||
setEditDeviceId(null);
|
||||
setEditDeviceName("");
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={styles.container}>
|
||||
<h1 style={styles.title}>Устройства</h1>
|
||||
<div style={styles.addDevice}>
|
||||
<input
|
||||
type="text"
|
||||
value={newDeviceName}
|
||||
onChange={(e) => setNewDeviceName(e.target.value)}
|
||||
placeholder="Название нового устройства"
|
||||
style={styles.input}
|
||||
/>
|
||||
<button onClick={addDevice} style={styles.addButton}>Добавить устройство</button>
|
||||
</div>
|
||||
|
||||
<ul style={styles.deviceList}>
|
||||
{devices.map((device) => (
|
||||
<li key={device.id} style={styles.deviceItem}>
|
||||
{editDeviceId === device.id ? (
|
||||
<div style={styles.editContainer}>
|
||||
<input
|
||||
type="text"
|
||||
value={editDeviceName}
|
||||
onChange={(e) => setEditDeviceName(e.target.value)}
|
||||
placeholder="Новое имя устройства"
|
||||
style={styles.input}
|
||||
/>
|
||||
<button onClick={saveDeviceEdit} style={styles.saveButton}>Сохранить</button>
|
||||
<button onClick={() => setEditDeviceId(null)} style={styles.cancelButton}>Отмена</button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<b style={styles.deviceName}>{device.name}</b>
|
||||
<button onClick={() => startEditDevice(device)} style={styles.editButton}>
|
||||
Редактировать
|
||||
</button>
|
||||
<button onClick={() => removeDevice(device.id)} style={styles.deleteButton}>
|
||||
Удалить
|
||||
</button>
|
||||
{/* Генерация QR-кода для устройства */}
|
||||
<QRCodeCanvas value={`Device ID: ${device.id}, Device Name: ${device.name}`} />
|
||||
</>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
padding: "20px",
|
||||
backgroundColor: "#f5f5f5",
|
||||
borderRadius: "8px",
|
||||
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.1)",
|
||||
},
|
||||
title: {
|
||||
marginBottom: "20px",
|
||||
color: "#333",
|
||||
},
|
||||
addDevice: {
|
||||
marginBottom: "20px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
input: {
|
||||
padding: "10px",
|
||||
marginRight: "10px",
|
||||
border: "1px solid #ccc",
|
||||
borderRadius: "4px",
|
||||
flexGrow: 1,
|
||||
},
|
||||
addButton: {
|
||||
padding: "10px 15px",
|
||||
backgroundColor: "#3498db",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
transition: "background-color 0.3s",
|
||||
},
|
||||
addButtonHover: {
|
||||
backgroundColor: "#2980b9",
|
||||
},
|
||||
deviceList: {
|
||||
listStyleType: "none",
|
||||
padding: "0",
|
||||
},
|
||||
deviceItem: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: "5px",
|
||||
padding: "15px",
|
||||
marginBottom: "10px",
|
||||
boxShadow: "0 1px 5px rgba(0, 0, 0, 0.1)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
},
|
||||
editContainer: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
deviceName: {
|
||||
flexGrow: 1,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
editButton: {
|
||||
marginLeft: "10px",
|
||||
padding: "5px 10px",
|
||||
backgroundColor: "#f39c12",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
deleteButton: {
|
||||
marginLeft: "10px",
|
||||
padding: "5px 10px",
|
||||
backgroundColor: "#e74c3c",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
saveButton: {
|
||||
marginLeft: "10px",
|
||||
padding: "5px 10px",
|
||||
backgroundColor: "#2ecc71",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
cancelButton: {
|
||||
marginLeft: "10px",
|
||||
padding: "5px 10px",
|
||||
backgroundColor: "#95a5a6",
|
||||
color: "white",
|
||||
border: "none",
|
||||
borderRadius: "4px",
|
||||
cursor: "pointer",
|
||||
},
|
||||
};
|
||||
|
||||
export default DevicesPage;
|
Loading…
Reference in New Issue
Block a user