Этот коммит содержится в:
viktor138irk
2025-07-11 22:17:56 +09:00
коммит произвёл GitHub
родитель f5dc4a3506
Коммит 89ccb7fa63
14 изменённых файлов: 7265 добавлений и 0 удалений
+180
Просмотреть файл
@@ -0,0 +1,180 @@
* { box-sizing: border-box; }
body {
font-family: Arial, sans-serif;
background: #f3f4f6;
margin: 0;
padding: 0;
}
header {
background: #1f2937;
color: #fff;
padding: 15px 30px;
font-size: 20px;
font-weight: bold;
}
.container {
display: flex;
padding: 30px;
gap: 30px;
align-items: flex-start;
}
.left-section { flex: 3; }
.right-section {
flex: 1;
position: sticky;
top: 30px;
background: #fff;
border-radius: 10px;
padding: 20px;
box-shadow: 0 0 10px rgba(0,0,0,0.05);
}
.user-table {
width: 100%;
background: #fff;
border-collapse: collapse;
margin-top: 1em;
}
.user-table th, .user-table td {
border: 1px solid #ccc;
padding: 8px 8px;
text-align: left;
vertical-align: center;
}
.user-table th {
background-color: #f4f4f4;
}
.user-table th:last-child,
.user-table td.button-group {
width: 90px;
text-align: center;
white-space: nowrap;
}
.unconfirmed-card {
background: #fff;
padding: 5px;
margin-bottom: 1px;
border-radius: 8px;
box-shadow: 0 0 4px rgba(0,0,0,0.05);
display: flex;
justify-content: space-between;
align-items: center;
}
.card-space {
display: flex;
justify-content: space-between;
align-items: center;
}
.card {
background: #fff;
padding: 15px;
margin-bottom: 10px;
border-radius: 8px;
box-shadow: 0 0 4px rgba(0,0,0,0.05);
display: flex;
justify-content: space-between;
align-items: center;
}
.card span { font-size: 14px; }
.button-group {white-space: nowrap;
text-align: center;
flex-direction: row;
padding: 4px;}
.card a, .button-group a { display: inline-block;
padding: 4px 8px;
margin: 0 2px;
font-size: 0.9em;
background-color: #f4f4f4; color: white;
border: 1px solid #ccc;
text-decoration: none;
transition: background-color 0.3s ease;
border-radius: 4px;}
.card a:hover { background-color: #f4f4f4; color:#1f2937; font-weight:bold;}
.button-group a:hover { background-color: #d7d7d7; color:#1f2937;}
.confirm { color: #10b981; font-weight: bold; text-decoration: none; }
.delete { color: #ef4444; text-decoration: none; }
.add-form input {
padding: 10px;
margin-bottom: 10px;
width: 100%;
border-radius: 5px;
border: 1px solid #ccc;
}
.add-form button {
transition: background-color 0.3s ease;
padding: 10px;
background: #1f2937;
color: white;
border: none;
border-radius: 5px;
font-weight: bold;
width: 100%;
cursor: pointer;
}
.add-form button:hover {
padding: 10px;
background: #f4f4f4;
color: #1f2937;
border: none;
border-radius: 5px;
font-weight: bold;
width: 100%;
cursor: pointer;
}
.form-error {
color: red;
font-size: 13px;
margin-top: -8px;
margin-bottom: 10px;
}
.sync-button {
transition: background-color 0.3s ease;
background: #ccc;
color: #1f2937;
border: none;
padding: 8px 14px;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
}
.sync-button:hover {
background: white;
color: black;
font-weight: bold;
border: none;
padding: 8px 14px;
font-size: 14px;
border-radius: 5px;
cursor: pointer;
}
.search-form {
margin-bottom: 1rem;
font-weight: bold;
}
.search-form input {
padding: 6px;
font-size: 1rem;
}
.search-form button {
padding: 6px 10px;
border-radius: 5px;
font-weight: bold;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease;
}
.search-form button:hover {background-color: #1f2937; color: white; border-radius: 5px;}
.pagination { margin-top: 15px; text-align: center; }
.pagination a {padding: 6px 12px; border: 1px solid #ccc; border-radius: 4px; background-color: #f2f2f2; color: #333; text-decoration: none; transition: background-color 0.2s ease;}
a { color: black; text-decoration: none;}
a:hover,
a:focus,
a:visited,
a:active {
text-decoration: none;
}
.pagination .current { background: #1f2937; color: #fff; }
h2, h3 { margin-top: 0; }
+125
Просмотреть файл
@@ -0,0 +1,125 @@
//Две функции в файле - добавление и редактирование. Загружаются при загрузке странице,
//ищут переменные по id или имени, добавляются к переменным через EventListener
document.addEventListener("DOMContentLoaded", () => {
const idInput = document.querySelector('input[name="new_id"]');
const csInput = document.querySelector('input[name="new_callsign"]');
const submitBtn = document.getElementById('submit-btn');
const idError = document.getElementById('id-error');
const csError = document.getElementById('callsign-error');
const latinError = document.getElementById('latin-error');
const optionalInputs = document.querySelectorAll('.add-form input[type="text"]:not([name="new_callsign"]):not([name="remarks"])');
async function check() {
const id = idInput.value.trim();
const callsign = csInput.value.trim();
idError.textContent = '';
csError.textContent = '';
latinError.textContent = '';
submitBtn.disabled = false;
//Вызываем метод из логики на валидацию
const res = await fetch(`?ajax_check=1&id=${encodeURIComponent(id)}&callsign=${encodeURIComponent(callsign)}`);
const json = await res.json();
if (json.idInvalid) {
idError.textContent = 'ID должен быть 6–7 цифр.';
} else if (json.idExists) {
idError.textContent = 'Такой ID уже существует.';
}
if (json.callsignInvalid) {
csError.textContent = 'Позывной: 4–7 латинских символов/цифр.';
} else if (json.callsignExists) {
csError.textContent = 'Такой позывной уже существует.';
}
if (json.idInvalid || json.idExists || json.callsignInvalid || json.callsignExists) {
submitBtn.disabled = true;
}
for (const input of optionalInputs) {
if (input.value && !/^[a-zA-Z\s]*$/.test(input.value)) {
latinError.textContent = 'Дополнительные поля должны содержать только латиницу.';
submitBtn.disabled = true;
break;
}
}
}
//Добавляем функцию на ввод
idInput.addEventListener('input', check);
csInput.addEventListener('input', check);
optionalInputs.forEach(input => input.addEventListener('input', check));
});
//Редактирование
document.addEventListener('DOMContentLoaded', function () {
//По клику получаем ближайшую ячеку и строку
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function (e) {
e.preventDefault();
const td = btn.closest('td');
const tr = td.closest('tr');
const id = tr.cells[0].innerText;
if (tr.classList.contains('editing')) return;
tr.classList.add('editing');
const cells = tr.querySelectorAll('td');
const headers = ['id', 'callsign', 'fname', 'surname', 'city', 'state', 'country', 'remarks'];
//Проходимся по ячейкам и меняем то, что внутри, ограничиваем позывной и телеграм
for (let i = 1; i < cells.length - 1; i++) {
const field = headers[i];
const value = cells[i].textContent.trim();
console.log(field);
let inputHTML = `<input type="text" name="${field}" value="${value}" style="width: 100%"`;
if (field === 'callsign') {
inputHTML += ` maxlength="7" required>`;
cells[i].innerHTML = inputHTML;
const input = cells[i].querySelector('input');
input.addEventListener('input', () => {
input.value = input.value.replace(/[^A-Za-z0-9]/g, '');
});
} else if (field === 'remarks') {
inputHTML += ` maxlength="32">`;
cells[i].innerHTML = inputHTML;
const input = cells[i].querySelector('input');
input.addEventListener('input', () => {
input.value = input.value.replace(/[^A-Za-z0-9_]/g, '');
});
} else {
inputHTML += '>';
cells[i].innerHTML = inputHTML;
}
}
//Меняем внутреннее содержимое ячейки
td.innerHTML = `
<button class="save-btn">💾</button>
<button class="cancel-btn">↩️</button>
`;
//Отправляем в класс с логикой
td.querySelector('.save-btn').addEventListener('click', () => {
const data = { "id": id};
console.log('click on test');
for (let i = 1; i < cells.length - 1; i++) {
const input = cells[i].querySelector('input');
data[headers[i]] = input.value;
}
fetch('?edit=1', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(res => {location.reload();});
});
//Просто перезагружаем страницу - ничего не отправляем
td.querySelector('.cancel-btn').addEventListener('click', () => location.reload());
});
});
});