balance_wheel/views/admin/index.hbs
2023-03-26 14:40:47 +07:00

892 lines
No EOL
41 KiB
Handlebars
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<style>
.chart-container {
position: relative;
margin: auto;
height: 25rem;
width: 100%;
}
</style>
<div id="root" x-data="{ loading: true }" x-init="fetchData()" x-ref="root">
<div x-show="loading" class="position-absolute spinner-border start-50 top-50" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div x-show="!loading" x-data="{ tab: $persist('vgroups') }" x-ref="tabs-holder" x-cloak x-transition>
<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" @click="tab = 'vgroups'">
<a :class="{ 'active': tab === 'vgroups' }" @click.prevent="tab = 'vgroups'" href="#vgroups" class="nav-link" aria-current="page" href="#vgroups">Группы</a>
</li>
<li class="nav-item" @click="tab = 'voters'">
<a :class="{ 'active': tab === 'voters' }" @click.prevent="tab = 'voters'" href="#voters" class="nav-link" href="#voters">Пользователи</a>
</li>
<li class="nav-item" @click="tab = 'votes'">
<a :class="{ 'active': tab === 'votes' }" @click.prevent="tab = 'votes'" href="#votes" class="nav-link" href="#votes">Голоса</a>
</li>
<li class="nav-item" @click="tab = 'analysis'">
<a :class="{ 'active': tab === 'analysis' }" @click.prevent="tab = 'analysis'" href="#analysis" class="nav-link" href="#analysis">Анализ</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 x-show="tab === 'vgroups'">
<form x-data="{ newModelName: '', newModelDescription: '' }" class="mb-4 d-flex flex-row gap-2" action="/admin/vgroups/create" method="POST">
<input type="text" x-model="newModelName" name="name" class="form-control form-control-sm flex-grow-1" placeholder="Название">
<input type="text" x-model="newModelDescription" name="description" class="form-control form-control-sm flex-grow-1" placeholder="Описание">
<button x-bind:disabled="newModelName.length < 3" class="btn btn-info btn-sm w-50">Создать новую группу</button>
</form>
<table x-data="{ titleSearch: '' }" id="vgroupsTable" class="w-100 table table-sm table-bordered">
<thead>
<tr>
<th scope="col" class="align-middle text-center">ID</th>
<th scope="col" class="align-middle text-start">
Название
<input type="text" class="form-control form-control-sm" placeholder="Поиск"
@input="titleSearch = $event.target.value;">
</th>
<th scope="col" class="align-middle text-center">Описание</th>
<th scope="col" class="align-middle text-center">Действия</th>
</tr>
</thead>
<tbody class="table-group-divider">
<template x-if="!$store.dataset.vgroups">
<tr><td colspan="4" scope="row"><i>Loading...</i></td></tr>
</template>
<template x-for="vgroup in $store.dataset.vgroups">
<tr x-show="vgroup.name.includes(titleSearch)">
<td x-text="vgroup.id" scope="row"></td>
<td x-text="vgroup.name"></td>
<td x-text="vgroup.description || '[Нет описания]'"></td>
<td>
<button class="btn btn-sm btn-outline-danger"
@click="deleteVgroup(vgroup.id)">
Удалить
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div x-show="tab === 'voters'">
<form x-data="{ newVoterFullName: '', newVoterLogin: '', newVoterPassword: '' }" class="mb-4 d-flex flex-row gap-2" action="/admin/voters/create" method="POST">
<input type="text" x-model="newVoterFullName" name="full_name" class="form-control form-control-sm flex-grow-1" placeholder="Полное имя">
<input type="text" x-model="newVoterLogin" name="login" class="form-control form-control-sm flex-grow-1" placeholder="Логин">
<input type="text" x-model="newVoterPassword" name="password" class="form-control form-control-sm flex-grow-1" placeholder="Пароль">
<button x-bind:disabled="newVoterFullName.length < 4 || newVoterLogin.length < 5 || newVoterPassword.length < 4" class="btn btn-info btn-sm w-50">Создать пользователя</button>
</form>
<table x-data="{ groupFilter: -1 }" id="votersTable" class="w-100 table table-sm table-bordered">
<thead>
<tr>
<th scope="col" class="align-middle text-center">ID</th>
<th scope="col" class="align-middle text-center">Имя</th>
<th scope="col" class="align-middle text-center">Логин</th>
<th scope="col" class="align-middle text-start">
Группа
<select class="form-select form-select-sm" @change="groupFilter = $event.target.value">
<option value="-1" selected>Без фильтра</option>
<template x-for="vgroup in $store.dataset.vgroups">
<option x-bind:value="vgroup.id" x-text="vgroup.name"></option>
</template>
</select>
</th>
<th scope="col" class="align-middle text-center">Действия</th>
</tr>
</thead>
<tbody class="table-group-divider">
<template x-if="!$store.dataset.voters">
<tr><td colspan="5" scope="row"><i>Loading...</i></td></tr>
</template>
<template x-for="voter in $store.dataset.voters">
<tr x-show="groupFilter == -1 || groupFilter == voter.vgroup_id">
<td x-text="voter.id" scope="row"></td>
<td x-text="voter.full_name"></td>
<td x-text="voter.login"></td>
<td>
<template x-if="!$store.dataset.vgroups">
<tr><td colspan="4" scope="row"><i>Loading...</i></td></tr>
</template>
<select class="form-select form-select-sm"
@change="updateUserGroup(voter.id, $event.target.value)">
<option value="null" x-bind:selected="voter.vgroup_id === null">Не привязан</option>
<template x-for="vgroup in $store.dataset.vgroups">
<option x-bind:value="vgroup.id" x-text="vgroup.name" x-bind:selected="voter.vgroup_id === vgroup.id"></option>
</template>
</select>
</td>
<td>
<button class="btn btn-sm btn-outline-danger"
@click="deleteVoter(voter.id)">
Удалить
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div x-show="tab === 'votes'" x-data="{ colorCoded: false }">
<div class="form-check form-switch form-check-reverse">
<input class="form-check-input" type="checkbox" id="flexSwitchCheckReverse" @change="colorCoded = $event.target.checked">
<label class="form-check-label" for="flexSwitchCheckReverse">Колор-кодинг</label>
</div>
<table x-data="{ groupFilter: -1, dateFilter: '' }" id="votesTable" class="w-100 table table-sm table-bordered">
<thead>
<tr>
<th scope="col" class="align-middle text-center">
Когда
<select class="form-select form-select-sm"
@change="dateFilter = $event.target.value">
<option value="" selected>Без фильтра</option>
<template x-for="voteDate in getDatesFromDatasets()">
<option x-bind:value="voteDate" x-text="voteDate"></option>
</template>
</select>
</th>
<th scope="col" class="align-middle text-center">Кто</th>
<th scope="col" class="align-middle text-start">
Группа
<select class="form-select form-select-sm" @change="groupFilter = $event.target.value">
<option value="-1" selected>Без фильтра</option>
<template x-for="vgroup in $store.dataset.vgroups">
<option x-bind:value="vgroup.id" x-text="vgroup.name"></option>
</template>
</select>
</th>
<th scope="col" class="align-middle text-center wm-vlr">Здоровье</th>
<th scope="col" class="align-middle text-center wm-vlr">Любовь</th>
<th scope="col" class="align-middle text-center wm-vlr">Секс</th>
<th scope="col" class="align-middle text-center wm-vlr">Работа</th>
<th scope="col" class="align-middle text-center wm-vlr">Отдых</th>
<th scope="col" class="align-middle text-center wm-vlr">Деньги</th>
<th scope="col" class="align-middle text-center wm-vlr">Отношения</th>
<th scope="col" class="align-middle text-center wm-vlr">Личн. рост</th>
<th scope="col" class="align-middle text-center wm-vlr">Смысл жизни</th>
<th scope="col" class="align-middle text-center wm-vlr">Тревожность</th>
<th scope="col" class="align-middle text-center">Действия</th>
</tr>
</thead>
<tbody class="table-group-divider">
<template x-if="!$store.dataset.votes">
<tr><td colspan="14" scope="row"><i>Loading...</i></td></tr>
</template>
<template x-for="vote in $store.dataset.votes">
<tr
x-data="{ voter: $store.dataset.voters.find((el) => el.id == vote.voter_id), vfd: formatDateMMYYYY(new Date(vote.vote_date)) }"
x-show="(groupFilter == -1 || groupFilter == voter.vgroup_id) && (vfd.includes(dateFilter))">
<td x-text="vfd" scope="row"></td>
<td x-text="voter?.full_name || '[Нет имени]'"></td>
<td x-text="getVGroupNameById(voter.vgroup_id)"></td>
<td class="fading-bg stats-cell" x-text="vote.health"
:class="{
'status-ok': colorCoded && vote.health >= 7,
'status-warning': colorCoded && vote.health < 7 && vote.health > 4,
'status-dangerous': colorCoded && vote.health <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.love"
:class="{
'status-ok': colorCoded && vote.love >= 7,
'status-warning': colorCoded && vote.love < 7 && vote.love > 4,
'status-dangerous': colorCoded && vote.love <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.sex"
:class="{
'status-ok': colorCoded && vote.sex >= 7,
'status-warning': colorCoded && vote.sex < 7 && vote.sex > 4,
'status-dangerous': colorCoded && vote.sex <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.work"
:class="{
'status-ok': colorCoded && vote.work >= 7,
'status-warning': colorCoded && vote.work < 7 && vote.work > 4,
'status-dangerous': colorCoded && vote.work <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.rest"
:class="{
'status-ok': colorCoded && vote.rest >= 7,
'status-warning': colorCoded && vote.rest < 7 && vote.rest > 4,
'status-dangerous': colorCoded && vote.rest <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.finances"
:class="{
'status-ok': colorCoded && vote.finances >= 7,
'status-warning': colorCoded && vote.finances < 7 && vote.finances > 4,
'status-dangerous': colorCoded && vote.finances <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.relations"
:class="{
'status-ok': colorCoded && vote.relations >= 7,
'status-warning': colorCoded && vote.relations < 7 && vote.relations > 4,
'status-dangerous': colorCoded && vote.relations <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.pers_growth"
:class="{
'status-ok': colorCoded && vote.pers_growth >= 7,
'status-warning': colorCoded && vote.pers_growth < 7 && vote.pers_growth > 4,
'status-dangerous': colorCoded && vote.pers_growth <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.meaning_of_life"
:class="{
'status-ok': colorCoded && vote.meaning_of_life >= 7,
'status-warning': colorCoded && vote.meaning_of_life < 7 && vote.meaning_of_life > 4,
'status-dangerous': colorCoded && vote.meaning_of_life <= 4
}" scope="row"></td>
<td class="fading-bg stats-cell" x-text="vote.serenity"
:class="{
'status-ok': colorCoded && vote.serenity <= 4,
'status-warning': colorCoded && vote.serenity > 4 && vote.serenity < 7,
'status-dangerous': colorCoded && vote.serenity >= 7
}" scope="row"></td>
<td>
<button class="btn btn-sm btn-outline-danger"
@click="deleteVote(vote.id)">
Удалить
</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
<div x-show="tab === 'analysis'" x-data="{
show_acloub: $persist(true),
show_agc: $persist(true),
shown_agac: $persist(true),
shown_gc: $persist(true),
shown_cat: $persist(true),
}">
<div class="align-items-center d-flex flex-row justify-content-between">
<h4>Среднее за клуб</h4>
<button class="badge text-bg-secondary"
@click="show_acloub = !show_acloub"
x-text="show_acloub? 'Скрыть' : 'Показать'"></button>
</div>
<div x-show="show_acloub" x-transition>
<div class="chart-container">
<canvas chart-id="clubAverageCategories" width="400" height="200"></canvas>
</div>
</div>
<hr>
<div class="align-items-center d-flex flex-row justify-content-between">
<h4>Среднее по категориям в группах</h4>
<button class="badge text-bg-secondary"
@click="show_agc = !show_agc"
x-text="show_agc? 'Скрыть' : 'Показать'"></button>
</div>
<div x-show="show_agc" x-transition>
<div class="m-0 mb-1 mb-2 row">
<select class="form-select form-select-sm col me-1"
@change="$store.dataset.selectedGacavVgroup = parseInt($event.target.value); updateGACChart()">
<template x-for="vgroup_id in Object.keys($store.dataset.grouppedVotes)">
<option x-bind:value="vgroup_id" x-text="getVGroupNameById(vgroup_id)" x-bind:selected="$store.dataset.selectedGacavVgroup === vgroup_id"></option>
</template>
</select>
</div>
<div class="chart-container">
<canvas chart-id="groupAverageCategories" width="400" height="200"></canvas>
</div>
</div>
<hr>
<div class="align-items-center d-flex flex-row justify-content-between">
<h4>Среднее за по категориям среди всех групп</h4>
<button class="badge text-bg-secondary"
@click="shown_agac = !shown_agac"
x-text="shown_agac? 'Скрыть' : 'Показать'"></button>
</div>
<div x-show="shown_agac" x-transition>
<div class="m-0 mb-1 mb-2 row">
<select class="form-select form-select-sm col ms-1"
@change="$store.dataset.agacVoteDate = $event.target.value; updateAGACChart()">
<template x-for="voteDate in getDatesFromDatasets()">
<option x-bind:value="voteDate" x-text="voteDate" x-bind:selected="$store.dataset.agacVoteDate === voteDate"></option>
</template>
</select>
</div>
<div class="chart-container">
<canvas chart-id="allGroupAverageCategories" width="400" height="200"></canvas>
</div>
</div>
<hr>
<div class="align-items-center d-flex flex-row justify-content-between">
<h4>Значения в группе по категориям за дату</h4>
<button class="badge text-bg-secondary"
@click="shown_gc = !shown_gc"
x-text="shown_gc? 'Скрыть' : 'Показать'"></button>
</div>
<div x-show="shown_gc" x-transition>
<div class="m-0 mb-1 mb-2 row">
<select class="form-select form-select-sm col me-1"
@change="$store.dataset.selectedRadarVgroup = parseInt($event.target.value); updateRadarChart()">
<template x-for="vgroup_id in Object.keys($store.dataset.grouppedVotes)">
<option x-bind:value="vgroup_id" x-text="getVGroupNameById(vgroup_id)" x-bind:selected="$store.dataset.selectedRadarVgroup === vgroup_id"></option>
</template>
</select>
<select class="form-select form-select-sm col ms-1"
@change="$store.dataset.voteDate = $event.target.value; updateRadarChart()">
<template x-for="voteDate in getDatesFromDatasets()">
<option x-bind:value="voteDate" x-text="voteDate" x-bind:selected="$store.dataset.voteDate === voteDate"></option>
</template>
</select>
</div>
<div id="lowDatasetCountNotification" class="alert alert-info" role="alert"
x-show="$store.dataset.grouppedVotes[$store.dataset.selectedRadarVgroup] && !$store.dataset.grouppedVotes[$store.dataset.selectedRadarVgroup].find(e => e.vote_date == $store.dataset.voteDate)"
x-intersect:enter="$store.dynamicsChart = 'bar'">
У группы нет данных за эту дату
</div>
<div class="chart-container">
<canvas chart-id="groupCategories" width="400" height="200"></canvas>
</div>
</div>
<hr>
<div class="align-items-center d-flex flex-row justify-content-between">
<h4>Динамика категорий группы во времени (среднее)</h4>
<button class="badge text-bg-secondary"
@click="shown_cat = !shown_cat"
x-text="shown_cat? 'Скрыть' : 'Показать'"></button>
</div>
<div x-show="shown_cat" x-transition>
<div class="m-0 mb-1 mb-2 row">
<select class="form-select form-select-sm col"
@change="$store.dataset.selectedVgroup = parseInt($event.target.value); updateLineChart()">
<template x-for="vgroup_id in Object.keys($store.dataset.grouppedVotes)">
<option x-bind:value="vgroup_id" x-text="getVGroupNameById(vgroup_id)" x-bind:selected="$store.dataset.selectedVgroup === vgroup_id"></option>
</template>
</select>
</div>
<div id="lowDatasetCountNotification" class="alert alert-info" role="alert"
x-show="$store.dataset.grouppedVotes[$store.dataset.selectedVgroup] && $store.dataset.grouppedVotes[$store.dataset.selectedVgroup].length == 1"
x-intersect:enter="$store.dynamicsChart = 'bar'">
Мало данных по группе, лучше использовать столбчатую диаграмму
</div>
<div class="chart-container">
<canvas chart-id="categoryBars" width="400" height="300"></canvas>
</div>
</div>
<div class="pb-5 pt-5"></div>
</div>
</div>
</div>
</div>
<script>
const randomNum = () => Math.floor(Math.random() * (235 - 52 + 1) + 52);
const randomRGB = () => `rgba(${randomNum()}, ${randomNum()}, ${randomNum()}, 0.5)`;
const randomRGBO = (opacity) => `rgba(${randomNum()}, ${randomNum()}, ${randomNum()}, ${opacity})`;
const randomRGBV = () => [randomNum(), randomNum(), randomNum()];
const average = (arr) => (arr.reduce((acc, c) => acc + c, 0) / arr.length);
const fetchDatasetCategory = (dataset, group, category) => dataset[group].map(e => average(e[category]));
const getVGroupNameById = (vgroup_id) => Alpine.store("dataset").vgroups.find((el) => el.id == vgroup_id)?.name || '[Нет группы]';
const getDatesFromDatasets = () => [...new Set(Object.values(Alpine.store("dataset").grouppedVotes).map(e => e.map(i => i.vote_date)).reduce((acc, v) => [...acc, ...v], []))].sort();
const formatDateMMYYYY = (date) => date.toLocaleString('ru', { month: "numeric", year: "numeric" });
const getColorBasedOnValue = (value) => {
if (value <= 4) return 'rgba(255, 0, 0, 0.5)';
if (value > 4 && value < 7) return 'rgba(255, 255, 0, 0.5)';
return 'rgba(0, 255, 0, 0.5)';
};
</script>
<script src="/javascripts/chart.min.js"></script>
<script src="/javascripts/chartjs-plugin-datalabels-v2.1.0.js"></script>
<script>
const dbToCategory = {
health: "Здоровье",
love: "Любовь",
sex: "Секс",
rest: "Отдых",
finances: "Деньги",
meaning_of_life: "Смысл жизни",
serenity: "Тревожность",
relations: "Отношения",
pers_growth: "Личн. рост",
work: "Работа",
};
class ChartsController {
constructor() {
this.charts = {};
}
createChart(id, type, data, options) {
const ctx = document.querySelector(`[chart-id=${id}]`).getContext('2d');
this.charts[id] = {
ctx,
chart: new Chart(ctx, {
type: type,
data: data,
options: options,
})
};
}
updateChartData(id, labels, data) {
this.charts[id].chart.data.labels.length = 0;
this.charts[id].chart.data.datasets.length = 0;
this.charts[id].chart.data.labels.push(...labels);
this.charts[id].chart.data.datasets.push(...data);
this.charts[id].chart.update();
}
}
</script>
<script>
Chart.register(ChartDataLabels);
Chart.defaults.set("plugins.datalabels", {display: false});
const ct = new ChartsController();
let rootElement;
document.addEventListener("DOMContentLoaded", function() {
rootElement = document.getElementById("root");
});
document.addEventListener('alpine:init', () => {
Alpine.store("dataset", {
vgroups: [],
voters: [],
votes: [],
grouppedVotes: {},
});
ct.createChart("clubAverageCategories", "radar", {}, {
maintainAspectRatio: false,
plugins: {},
scales: {
v: {
min: 0,
max: 10,
axis: "r",
},
}
});
ct.createChart("groupAverageCategories", "radar", {}, {
maintainAspectRatio: false,
plugins: {},
scales: {
v: {
min: 0,
max: 10,
axis: "r",
pointLabels: {
display: true,
centerPointLabels: true,
},
},
}
});
ct.createChart("allGroupAverageCategories", "polarArea", {}, {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
v: {
min: 0,
max: 10,
axis: "r",
pointLabels: {
display: true,
centerPointLabels: true,
},
},
}
});
ct.createChart("groupCategories", "polarArea", {}, {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
},
scales: {
v: {
min: 0,
max: 10,
axis: "r",
pointLabels: {
display: true,
centerPointLabels: true,
},
},
}
});
ct.createChart("categoryBars", "bar", {}, {
maintainAspectRatio: false,
plugins: {
legend: {
display: false,
},
datalabels: {
display: true,
color: 'black',
font: {
size: 14
},
rotation: -90,
align: 'end',
anchor: 'end',
clip: true,
display: 'auto',
formatter: function(value, context) {
return context.dataset.label;
},
}
},
scales: {
y: {
min: 0,
max: 12,
},
x: {
min: 0,
max: 10,
}
}
});
});
const fetchData = async function() {
Alpine.store("dataset").vgroups = [];
Alpine.store("dataset").voters = [];
Alpine.store("dataset").votes = [];
Alpine.store("dataset").grouppedVotes = {};
try {
let resp;
resp = await fetch("/admin/vgroups");
Alpine.store("dataset").vgroups = await resp.json();
resp = await fetch("/admin/voters");
Alpine.store("dataset").voters = await resp.json();
resp = await fetch("/admin/votes");
Alpine.store("dataset").votes = await resp.json();
resp = await fetch("/admin/votes/groupped");
const data = await resp.json();
data.forEach((e) => {
Alpine.store("dataset").grouppedVotes[e.vgroup_id] = [...(Alpine.store("dataset").grouppedVotes[e.vgroup_id] || []), e];
});
try {
const vg = Object.keys(Alpine.store("dataset").grouppedVotes)[0];
Alpine.store("dataset").selectedVgroup = vg;
Alpine.store("dataset").selectedRadarVgroup = vg;
Alpine.store("dataset").selectedGacavVgroup = vg;
const ad = getDatesFromDatasets()[0];
Alpine.store("dataset").voteDate = ad;
Alpine.store("dataset").agacVoteDate = ad;
updateCACChart();
updateGACChart();
updateAGACChart();
updateAGACChart();
updateLineChart();
updateRadarChart();
} catch (e) {
alert("Произошла ошибка выделения данных для графиков, обратитесь к разработчику");
console.error(e);
}
} catch (e) {
alert("Ошибка загрузки данных");
console.error(e);
}
Alpine.$data(rootElement).loading = false;
}
const deleteVgroup = async function (vgroupId) {
try {
const resp = await fetch(`/admin/vgroups/${vgroupId}/delete`);
const json = await resp.json();
if (json.error) {
alert(json.error);
}
} catch (e) {
alert(e.message);
}
fetchData();
}
const updateUserGroup = async function (userId, newGroup) {
try {
const resp = await fetch(`/admin/voters/${userId}/promote/${newGroup}`);
const json = await resp.json();
if (json.error) {
alert(json.error);
}
} catch (e) {
alert(e.message);
}
fetchData();
}
const deleteVoter = async function (voterId) {
try {
const resp = await fetch(`/admin/voters/${voterId}/delete`);
const json = await resp.json();
if (json.error) {
alert(json.error);
}
} catch (e) {
alert(e.message);
}
fetchData();
}
const deleteVote = async function (voteId) {
try {
const resp = await fetch(`/admin/votes/${voteId}/delete`);
const json = await resp.json();
if (json.error) {
alert(json.error);
}
} catch (e) {
alert(e.message);
}
fetchData();
}
const updateCACChart = function () {
const baseDataset = Alpine.store("dataset").grouppedVotes;
const combinedDataset = {};
Object.values(Alpine.store("dataset").grouppedVotes).forEach(e => {
const rds = e.reduce((acc, v) => {
acc[v.vote_date] ??= {};
Object.keys(dbToCategory).forEach(key => {
acc[v.vote_date][key] ??= [];
acc[v.vote_date][key].push(...v[key])
});
return acc;
}, {});
Object.keys(rds).forEach(key => {
combinedDataset[key] ??= {};
Object.keys(rds[key]).forEach(kv => {
combinedDataset[key][kv] ??= [];
combinedDataset[key][kv].push(...rds[key][kv]);
});
});
}, {});
console.log(combinedDataset);
const labels = Object.keys(dbToCategory).map(key => dbToCategory[key]);
const datasets = Object.keys(combinedDataset).map(voteDate => {
const avgs = Object.keys(dbToCategory).map(key => average(combinedDataset[voteDate][key]));
const d = {
label: voteDate,
backgroundColor: randomRGBO(0.2), // FIIXME: randomize
pointBackgroundColor: avgs.map(getColorBasedOnValue),
borderWidth: 2,
data: avgs,
};
return d;
});
ct.updateChartData("clubAverageCategories", labels, datasets);
}
const updateGACChart = function () {
const [baseDataset, group] = [
Alpine.store("dataset").grouppedVotes,
Alpine.store("dataset").selectedGacavVgroup,
];
const labels = Object.keys(dbToCategory).map(key => dbToCategory[key]);
const datasets = baseDataset[group].map(e => {
const avgs = Object.keys(dbToCategory).map(key => e[key]).map(average);
const d = {
label: e.vote_date,
backgroundColor: randomRGBO(0.2), // FIIXME: randomize
pointBackgroundColor: avgs.map(getColorBasedOnValue),
borderWidth: 2,
data: avgs,
};
return d;
});
ct.updateChartData("groupAverageCategories", labels, datasets);
}
const update__Chart = function () {
const [baseDataset, voteDate] = [
Alpine.store("dataset").grouppedVotes,
Alpine.store("dataset").agacVoteDate,
];
const labels = [];
const datasets = [];
Object.keys(gvotes).forEach(cat => {
labels.push(dbToCategory[cat]);
// colors.push(getColorBasedOnValue(cat == "serenity" ? 10-av : av));
});
ct.updateChartData("allGroupAverageCategories", labels, datasets);
}
const updateAGACChart = function() {
const [baseDataset, voteDate] = [
Alpine.store("dataset").grouppedVotes,
Alpine.store("dataset").agacVoteDate,
];
const datasets = [];
const gvotes = {};
Object.values(baseDataset).forEach(vg => {
vg.forEach(ds => {
if (ds.vote_date === voteDate) {
Object.keys(dbToCategory).forEach(cat => {
const av = average(ds[cat]);
gvotes[cat] ??= [];
gvotes[cat].push(av);
});
}
});
});
const labels = [];
const colors = [];
const vals = [];
Object.keys(gvotes).forEach(cat => {
const av = average(gvotes[cat]);
labels.push(dbToCategory[cat]);
colors.push(getColorBasedOnValue(cat == "serenity" ? 10-av : av));
vals.push(av);
});
datasets.push({
label: labels,
backgroundColor: colors,
borderWidth: 2,
data: vals,
});
ct.updateChartData("allGroupAverageCategories", labels, datasets);
}
const updateRadarChart = function() {
const [baseDataset, group, voteDate] = [
Alpine.store("dataset").grouppedVotes,
Alpine.store("dataset").selectedRadarVgroup,
Alpine.store("dataset").voteDate,
];
const groupDataset = baseDataset[group].find(e => e.vote_date === voteDate);
const labels = [];
const datasets = [];
if (groupDataset) {
const vals = [];
Object.keys(dbToCategory).forEach(k => {
labels.push(dbToCategory[k]);
vals.push(average(groupDataset[k]));
});
const dataset = {
label: getVGroupNameById(group),
backgroundColor: vals.map((v, i) => getColorBasedOnValue(labels[i] == "Тревожность" ? 10-v : v)),
borderWidth: 2,
data: vals
};
datasets.push(dataset);
}
ct.updateChartData("groupCategories", labels, datasets);
}
const updateLineChart = function() {
const [baseDataset, group] = [Alpine.store("dataset").grouppedVotes, Alpine.store("dataset").selectedVgroup];
const labels = baseDataset[group].map(e => e.vote_date);
const datasets = [
{
label: "Здоровье",
backgroundColor: fetchDatasetCategory(baseDataset, group, "health").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "health")
},
{
label: "Любовь",
backgroundColor: fetchDatasetCategory(baseDataset, group, "love").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "love")
},
{
label: "Секс",
backgroundColor: fetchDatasetCategory(baseDataset, group, "sex").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "sex")
},
{
label: "Отдых",
backgroundColor: fetchDatasetCategory(baseDataset, group, "rest").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "rest")
},
{
label: "Деньги",
backgroundColor: fetchDatasetCategory(baseDataset, group, "finances").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "finances")
},
{
label: "Смысл жизни",
backgroundColor: fetchDatasetCategory(baseDataset, group, "meaning_of_life").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "meaning_of_life")
},
{
label: "Тревожность",
backgroundColor: fetchDatasetCategory(baseDataset, group, "serenity").map(e => getColorBasedOnValue(10-e)),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "serenity")
},
{
label: "Отношения",
backgroundColor: fetchDatasetCategory(baseDataset, group, "relations").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "relations")
},
{
label: "Личн. рост",
backgroundColor: fetchDatasetCategory(baseDataset, group, "pers_growth").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "pers_growth")
},
{
label: "Работа",
backgroundColor: fetchDatasetCategory(baseDataset, group, "work").map(getColorBasedOnValue),
borderWidth: 2,
data: fetchDatasetCategory(baseDataset, group, "work")
}
];
ct.updateChartData("categoryBars", labels, datasets);
}
</script>