meraproject/services/user-reader/static/project-member.html

154 lines
6.5 KiB
HTML
Raw Permalink Normal View History

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Состав проекта</title>
<style>
body { font-family: system-ui, sans-serif; margin: 1rem; max-width: 42rem; background: #111; color: #e6e6e6; }
h1 { font-size: 1.2rem; }
.meta { color: #888; font-size: 0.85rem; margin-bottom: 1rem; }
a { color: #8cf; }
.panel { margin-bottom: 1rem; padding: 0.75rem; background: #1a1a1a; border: 1px solid #333; }
label { display: block; margin: 0.5rem 0 0.25rem; font-size: 0.85rem; color: #aaa; }
input, select, button {
width: 100%; box-sizing: border-box; padding: 0.45rem;
background: #222; border: 1px solid #444; color: #e6e6e6; font-size: 0.95rem;
}
input[type="checkbox"] { width: auto; margin-right: 0.5rem; }
button { cursor: pointer; margin-top: 0.75rem; width: auto; padding: 0.5rem 1rem; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
pre { background: #0d0d0d; border: 1px solid #333; padding: 0.75rem; overflow: auto; font-size: 0.8rem; }
.hint { font-size: 0.8rem; color: #777; margin-top: 0.25rem; }
</style>
</head>
<body>
<nav class="meta">
<a href="/">Сотрудники</a> · <a href="/labor">Трудозатраты</a> · <a href="/time-entry">Запись часов</a> · <strong>Состав проекта</strong>
</nav>
<h1>Добавить / изменить участника</h1>
<p class="meta">Требуются права РП, ГИП или директора отдела. Acting = кто выполняет действие.</p>
<div id="keybox" class="panel" style="display:none;">
<label>API-ключ</label>
<input type="password" id="apiKeyInput" placeholder="local-dev-key-change-in-prod" />
<button type="button" id="saveKey">Сохранить</button>
</div>
<form id="form" class="panel">
<label for="actingId">Acting (X-Acting-Emp-Id)</label>
<input type="number" id="actingId" min="1" required />
<label for="projectId">Проект (id)</label>
<input type="number" id="projectId" min="1" required />
<label for="empId">Сотрудник (emp_id)</label>
<input type="number" id="empId" min="1" required />
<label for="sectionId">Раздел</label>
<select id="sectionId" required><option value="">— загрузите проект —</option></select>
<label for="roleId">Роль</label>
<select id="roleId" required><option value="">Загрузка…</option></select>
<label style="display:flex;align-items:center;margin-top:0.75rem;">
<input type="checkbox" id="active" checked /> Активен в команде
</label>
<label for="text">Комментарий</label>
<input type="text" id="text" />
<button type="submit">Сохранить (PUT)</button>
<button type="button" id="loadSections">Загрузить разделы проекта</button>
<button type="button" id="checkPerm">Проверить права</button>
</form>
<p id="status" class="meta"></p>
<pre id="result" hidden></pre>
<script>
const STORE_KEY = 'meraproject_user_reader_api_key';
function headers(json) {
const h = json ? { 'Content-Type': 'application/json' } : {};
const k = sessionStorage.getItem(STORE_KEY);
if (k) h['X-Api-Key'] = k;
return h;
}
function actingHeaders(json) {
const h = headers(json);
const id = document.getElementById('actingId').value.trim();
if (id) h['X-Acting-Emp-Id'] = id;
return h;
}
async function loadRoles() {
const r = await fetch('/api/member-roles', { headers: headers() });
if (r.status === 401) { document.getElementById('keybox').style.display = 'block'; return; }
const items = (await r.json()).items || [];
document.getElementById('roleId').innerHTML = items.map(x =>
'<option value="' + x.id + '">' + x.title + '</option>'
).join('');
}
async function loadSections() {
const pid = document.getElementById('projectId').value;
const status = document.getElementById('status');
if (!pid) { status.textContent = 'Укажите project_id'; return; }
const r = await fetch('/api/project-sections?project_id=' + pid, { headers: headers() });
const data = await r.json();
const sel = document.getElementById('sectionId');
const items = data.items || [];
sel.innerHTML = items.length
? items.map(s => '<option value="' + s.section_id + '">' + s.section_name + '</option>').join('')
: '<option value="">Нет разделов</option>';
status.textContent = 'Разделов: ' + items.length;
}
async function checkPerm() {
const acting = document.getElementById('actingId').value;
const emp = document.getElementById('empId').value;
const pid = document.getElementById('projectId').value;
const r = await fetch(
'/api/labor/permissions?target_emp_id=' + emp + '&project_id=' + pid,
{ headers: actingHeaders() }
);
document.getElementById('result').hidden = false;
document.getElementById('result').textContent = JSON.stringify(await r.json(), null, 2);
}
document.getElementById('form').addEventListener('submit', async (e) => {
e.preventDefault();
const status = document.getElementById('status');
const body = {
project_id: +document.getElementById('projectId').value,
emp_id: +document.getElementById('empId').value,
section_id: +document.getElementById('sectionId').value,
role: +document.getElementById('roleId').value,
active: document.getElementById('active').checked,
text: document.getElementById('text').value || ''
};
const r = await fetch('/api/project-members', {
method: 'PUT',
headers: actingHeaders(true),
body: JSON.stringify(body)
});
const data = await r.json();
document.getElementById('result').hidden = false;
document.getElementById('result').textContent = JSON.stringify(data, null, 2);
status.textContent = r.ok ? 'Сохранено' : 'Ошибка ' + r.status;
});
document.getElementById('loadSections').addEventListener('click', loadSections);
document.getElementById('checkPerm').addEventListener('click', checkPerm);
document.getElementById('saveKey').addEventListener('click', () => {
sessionStorage.setItem(STORE_KEY, document.getElementById('apiKeyInput').value);
loadRoles();
});
loadRoles();
</script>
</body>
</html>