Initial commit

This commit is contained in:
Andrew 2023-03-26 14:40:47 +07:00
commit 24f0a28a3c
25 changed files with 3190 additions and 0 deletions

410
views/userspace/index.hbs Normal file
View file

@ -0,0 +1,410 @@
<div class="h-screen d-flex flex-column">
<header class="navbar navbar-expand-md navbar-dark bd-navbar">
<ul class="nav nav-pills container flex-wrap flex-md-nowrap gap-2">
<h4>Привет, {{user.full_name}}!</h4>
<div class="flex-grow-1"></div>
<li class="nav-item">
<a class="nav-link active" href="/userspace/votes">История</a>
</li>
<li class="nav-item">
<a class="nav-link active" href="/gateway/logout">Выйти</a>
</li>
</ul>
</header>
<div x-data="{loading: false}" class="align-items-center container d-flex flex-grow-1 flex-row justify-content-center">
{{#if user.vgroup_id includeZero=true}}
<canvas id="wheel" style="height: 600"></canvas>
<form class="col m-4 h-fit-content">
<div class="d-flex flex-row gap-2">
<label class="w-15">Здоровье</label>
<input x-bind:disabled="loading" type="range" id="a0" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 0)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b0" min="0" max="10" step="1" value="0" oninput="param2Change(this, 0)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Любовь</label>
<input x-bind:disabled="loading" type="range" id="a1" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 1)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b1" min="0" max="10" step="1" value="0" oninput="param2Change(this, 1)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Секс</label>
<input x-bind:disabled="loading" type="range" id="a2" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 2)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b2" min="0" max="10" step="1" value="0" oninput="param2Change(this, 2)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Работа</label>
<input x-bind:disabled="loading" type="range" id="a3" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 3)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b3" min="0" max="10" step="1" value="0" oninput="param2Change(this, 3)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Отдых</label>
<input x-bind:disabled="loading" type="range" id="a4" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 4)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b4" min="0" max="10" step="1" value="0" oninput="param2Change(this, 4)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Деньги</label>
<input x-bind:disabled="loading" type="range" id="a5" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 5)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b5" min="0" max="10" step="1" value="0" oninput="param2Change(this, 5)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Отношения</label>
<input x-bind:disabled="loading" type="range" id="a6" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 6)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b6" min="0" max="10" step="1" value="0" oninput="param2Change(this, 6)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Личн. рост</label>
<input x-bind:disabled="loading" type="range" id="a7" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 7)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b7" min="0" max="10" step="1" value="0" oninput="param2Change(this, 7)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Смысл жизни</label>
<input x-bind:disabled="loading" type="range" id="a8" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 8)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b8" min="0" max="10" step="1" value="0" oninput="param2Change(this, 8)">
</div>
<div class="d-flex flex-row gap-2">
<label class="w-15">Тревожность</label>
<input x-bind:disabled="loading" type="range" id="a9" class="flex-grow-1" min="0" max="10" step="1" value="0" oninput="paramChange(this, 9)">
<input x-bind:disabled="loading" type="number" class="dubler" id="b9" min="0" max="10" step="1" value="0" oninput="param2Change(this, 9)">
</div>
<button x-bind:disabled="loading" type="button" id="sendBtn" class="btn btn-outline-primary w-100 pt-2">Отправить</button>
</form>
{{else}}
<div class="text-center">
<h1>Вы не состоите в группе</h1>
<p>Повторите попытку, когда администратор привяжет вас к группе</p>
</div>
{{/if}}
</div>
</div>
{{#if user.vgroup_id includeZero=true}}
<script>
let myCanvas, ctx;
let param = [{
name: "ЗДОРОВЬЕ",
dbmap: "health",
value: 0
},
{
name: "ЛЮБОВЬ",
dbmap: "love",
value: 0
},
{
name: "СЕКС",
dbmap: "sex",
value: 0
},
{
name: "РАБОТА",
dbmap: "work",
value: 0
},
{
name: "ОТДЫХ",
dbmap: "rest",
value: 0
},
{
name: "ДЕНЬГИ",
dbmap: "finances",
value: 0
},
{
name: "ОТНОШЕНИЯ",
dbmap: "relations",
value: 0
},
{
name: "ЛИЧН. РОСТ",
dbmap: "pers_growth",
value: 0
},
{
name: "СМЫСЛ ЖИЗНИ",
dbmap: "meaning_of_life",
value: 0
},
{
name: "СПОКОЙСТВИЕ",
dbmap: "serenity",
value: 0
},
];
document.addEventListener("DOMContentLoaded", () => {
myCanvas = document.getElementById("wheel");
myCanvas.width = 480;
myCanvas.height = 600;
ctx = myCanvas.getContext("2d");
drawWheel();
const sendBtn = document.getElementById("sendBtn");
sendBtn.addEventListener("click", async () => {
Alpine.$data(sendBtn).loading = true;
try {
let res = await fetch(`${location.href}/vote`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(param),
});
let json = await res.json();
console.log(json);
if (json.status == "ok") {
alert("Спасибо за голосование!");
} else {
alert(("Ошибка голосования!\n" + (json.message || "")).trim());
console.log(json);
}
} catch (e) {
console.error(e.message);
alert(e.message);
return;
}
resetWheel();
Alpine.$data(sendBtn).loading = false;
location.reload();
});
});
const resetWheel = () => {
document.querySelectorAll(".dubler").forEach((el) => {
el.value = 0;
});
document.querySelector(".dubler").dispatchEvent(new Event("input"));
}
// отрисовка линии
function drawLine(ctx, startX, startY, endX, endY) {
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();
}
// отрисовка дуги
function drawArc(ctx, centerX, centerY, radius, startAngle, endAngle) {
ctx.beginPath();
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.stroke();
}
// отрисовка сектора
function drawPieSlice(ctx, centerX, centerY, radius, startAngle, endAngle, color) {
ctx.fillStyle = color;
ctx.globalAlpha = 0.8;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.closePath();
ctx.fill();
}
// проставка временной отметки
function drawTimestamp(ctx) {
let now = new Date();
let day, month, hour, minute, second;
if (now.getDate() < 10) {
day = "0" + now.getDate();
} else {
day = now.getDate();
}
if (now.getMonth() < 10) {
month = "0" + (now.getMonth() + 1);
} else {
month = (now.getMonth() + 1);
}
if (now.getHours() < 10) {
hour = "0" + now.getHours();
} else {
hour = now.getHours();
}
if (now.getMinutes() < 10) {
minute = "0" + now.getMinutes();
} else {
minute = now.getMinutes();
}
if (now.getSeconds() < 10) {
second = "0" + now.getSeconds();
} else {
second = now.getSeconds();
}
let timestamp = day + "-" + month + "-" + now.getFullYear() + " " + hour + ":" + minute + ":" + second;
ctx.fillStyle = "darkgray";
ctx.globalAlpha = 1;
ctx.font = "10pt Roboto";
ctx.fillText(timestamp, myCanvas.width - 127, 15);
}
// отрисовка текста
function drawText(ctx) {
ctx.fillStyle = "#222";
name = " " + param[6].name + " " + param[7].name + " " + param[0].name + " " + param[1].name + " ";
drawTextAlongArc(ctx, name, 240, 240, 208, Math.PI);
name = " " + param[5].name + " " + param[4].name + " " + param[3].name + " " + param[2].name + " ";
drawTextAlongArc(ctx, name, 240, 240, -220, Math.PI * -1);
ctx.fillText(param[0].value, 283, 126);
ctx.fillText(param[1].value, 354, 196);
ctx.fillText(param[2].value, 354, 296);
ctx.fillText(param[3].value, 283, 366);
ctx.fillText(param[4].value, 190, 366);
ctx.fillText(param[5].value, 115, 296);
ctx.fillText(param[6].value, 115, 196);
ctx.fillText(param[7].value, 190, 126);
ctx.fillText(param[8].name + " - " + param[8].value, 45, 490);
ctx.fillText(param[9].name + " - " + (10 - param[9].value), 45, 550);
ctx.fillText("ТРЕВОЖНОСТЬ - " + param[9].value, 315, 550);
}
// отрисовка всего колеса
function drawWheel() {
ctx.clearRect(0, 0, myCanvas.width, myCanvas.height);
ctx.strokeStyle = "#999999";
ctx.fillStyle = "white";
ctx.rect(0, 0, myCanvas.width, myCanvas.height);
ctx.fill();
ctx.stroke();
ctx.fillStyle = "black";
ctx.globalAlpha = 1;
ctx.font = "11pt Roboto";
// отрисовка опорных кругов колеса
for (let i = 1; i <= 10; i++)
drawArc(ctx, 240, 240, i * 20, 0, 360);
// отрисовка опорных линий колеса
drawLine(ctx, 240, 40, 240, 440);
drawLine(ctx, 382, 98, 98, 382);
drawLine(ctx, 98, 98, 382, 382);
drawLine(ctx, 40, 240, 440, 240);
// отрисовка секторов колеса
for (let i = -0.5; i < 1.5; i = i + 0.25) {
value = param[(i + 0.5) / 0.25].value;
name = param[(i + 0.5) / 0.25].name;
if (value <= 4)
color = "#ea4335"; // красный
else if (value >= 8)
color = "#34a853"; // зеленый
else
color = "#fbbc05"; // желтый
drawPieSlice(ctx, 240, 240, 20 * value, Math.PI * i, Math.PI * (i + 0.25), color);
}
ctx.strokeStyle = "#999999";
for (let i = 0; i < 10; i = i + 1) {
ctx.rect(45 + i * 40, 500, 30, 20);
ctx.rect(45 + i * 40, 560, 30, 20);
ctx.stroke();
}
for (let i = 0; i < 10; i = i + 1) {
if (param[8].value < i + 1)
color = "white";
else
if (param[8].value <= 4)
color = "#ea4335" // красный
else if (param[8].value >= 8)
color = "#34a853"; // зеленый
else
color = "#fbbc05"; // желтый
ctx.fillStyle = color;
ctx.fillRect(45 + i * 40, 500, 30, 20);
////////////////////////////////////////////////////
if ((10 - param[9].value) < i + 1)
color = "white";
else
if ((10 - param[9].value) <= 4)
color = "#ea4335" // красный
else if ((10 - param[9].value) >= 8)
color = "#34a853"; // зеленый
else
color = "#fbbc05"; // желтый
ctx.fillStyle = color;
ctx.fillRect(45 + i * 40, 560, 30, 20);
}
drawTimestamp(ctx);
drawText(ctx);
}
// создание подписей вокруг внешнего круга
function drawTextAlongArc(context, str, centerX, centerY, radius, angle) {
let len = str.length,
s;
context.save();
context.translate(centerX, centerY);
context.rotate(-1 * angle / 2);
context.rotate(-1 * (angle / len) / 2);
for (let n = 0; n < len; n++) {
context.rotate(angle / len);
context.save();
context.translate(0, -1 * radius);
s = str[n];
context.fillText(s, 0, 0);
context.restore();
}
context.restore();
}
// действия при сдвиге ползунка
function paramChange(block, id) {
param[id].value = parseInt(block.value) || 0;
document.getElementById("b" + id).value = block.value;
drawWheel();
}
// действия при изменении значения дублирующего input
function param2Change(block, id) {
param[id].value = block.value;
document.getElementById("a" + id).value = block.value;
drawWheel();
}
</script>
{{/if}}

59
views/userspace/votes.hbs Normal file
View file

@ -0,0 +1,59 @@
<div>
<div>
<header class="navbar navbar-expand-md navbar-dark bd-navbar">
<ul class="nav nav-pills container flex-wrap flex-md-nowrap gap-2">
<li class="nav-item">
<a class="nav-link" href="/userspace">Вернуться</a>
</li>
<div class="flex-grow-1"></div>
<li class="nav-item">
<a class="nav-link active" href="/gateway/logout">Выйти</a>
</li>
</ul>
</header>
<div class="container p-0">
<div>
<table id="votesTable" class="w-100 table table-sm table-bordered">
<thead>
<tr>
<th scope="col">Когда</th>
<th scope="col">Здоровье</th>
<th scope="col">Любовь</th>
<th scope="col">Секс</th>
<th scope="col">Работа</th>
<th scope="col">Отдых</th>
<th scope="col">Деньги</th>
<th scope="col">Отношения</th>
<th scope="col">Личн. рост</th>
<th scope="col">Смысл жизни</th>
<th scope="col">Тревожность</th>
</tr>
</thead>
<tbody class="table-group-divider">
{{#each votes}}
<tr>
<td scope="row">{{date}}</td>
<td scope="row">{{health}}</td>
<td scope="row">{{love}}</td>
<td scope="row">{{sex}}</td>
<td scope="row">{{work}}</td>
<td scope="row">{{rest}}</td>
<td scope="row">{{finances}}</td>
<td scope="row">{{relations}}</td>
<td scope="row">{{pers_growth}}</td>
<td scope="row">{{meaning_of_life}}</td>
<td scope="row">{{serenity}}</td>
</tr>
{{else}}
<tr>
<td colspan="11" class="text-center">Нет данных</td>
</tr>
{{/each}}
</template>
</tbody>
</table>
</div>
</div>
</div>
</div>