Skip to content

Commit

Permalink
Merge branch 'memberships-chart' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjuhrich committed Jun 7, 2024
2 parents cebb2fd + 2430e99 commit 19b06bd
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 64 deletions.
1 change: 1 addition & 0 deletions bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const entryPoints = [
['tab-anchor', './js/tab-anchor.ts'],
['user-suite', './js/user-suite.ts'],
['rooms-table', './js/rooms-table.ts'],
['memberships-chart', './js/memberships-chart.ts'],
].map((x) => (
{out: x[0], in: path.join(src, x[1])}
))
Expand Down
81 changes: 21 additions & 60 deletions web/blueprints/user/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,56 +381,6 @@ def user_show(user_id: int) -> ResponseReturnValue:
tenancy_table=TenancyTable(
data_url=url_for(".tenancies_json", user_id=user.id)
),
tabs=[
{
'id': 'hosts',
'icon': 'fa-laptop',
'name': 'Hosts',
'badge': len(user.hosts)
},
{
'id': 'tasks',
'icon': 'fa-clipboard-check',
'name': 'Tasks',
'badge': len(user.tasks),
'badge_color': '#d9534f' if len(user.tasks) > 0 else None
},
{
'id': 'logs',
'icon': 'fa-list-ul',
'name': 'Logs',
'badge': len(user.log_entries)
},
{
'id': 'traffic',
'icon': 'fa-chart-area',
'name': 'Traffic',
},
{
'id': 'finance',
'icon': 'fa-euro-sign',
'name': 'Finanzen',
},
{
'id': 'groups',
'icon': 'fa-users-cog',
'name': 'Gruppen',
'badge': len(user.active_memberships())
},
{
'id': 'room_history',
'icon': 'fa-history',
'name': 'Wohnorte',
'badge': len(user.room_history_entries)
},
{
'id': 'tenancies',
'icon': 'fa-file-signature',
'name': 'Mietverträge',
'badge': len(user.tenancies),
'disabled': len(user.tenancies) == 0,
},
]
)


Expand Down Expand Up @@ -484,6 +434,7 @@ def user_show_groups_json(
return TableResponse[MembershipRow](
items=[
MembershipRow(
id=membership.id,
group_name=membership.group.name,
begins_at=datetime_format(
membership.active_during.begin,
Expand All @@ -496,13 +447,27 @@ def user_show_groups_json(
grants=granted or [],
denies=denied or [],
active=(active := (session.utcnow() in membership.active_during)),
actions=[
BtnColResponse(
href=url_for(
".edit_membership",
url_edit=(
url_edit := url_for(
".edit_membership",
user_id=user_id,
membership_id=membership.id,
)
),
url_end=(
url_end := (
url_for(
".end_membership",
user_id=user_id,
membership_id=membership.id,
),
)
if active
else None
)
),
actions=[
BtnColResponse(
href=url_edit,
title="Bearbeiten",
icon="fa-edit",
btn_class="btn-link",
Expand All @@ -511,11 +476,7 @@ def user_show_groups_json(
+ (
[
BtnColResponse(
href=url_for(
".end_membership",
user_id=user_id,
membership_id=membership.id,
),
href=url_end,
title="Beenden",
icon="fa-power-off",
btn_class="btn-link",
Expand Down
3 changes: 3 additions & 0 deletions web/blueprints/user/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@ class Meta:


class MembershipRow(BaseModel):
id: int
group_name: str
begins_at: DateColResponse
ends_at: DateColResponse
url_edit: str
url_end: str | None = None
actions: list[BtnColResponse]
# used by membershipRowAttributes
grants: list[str | None]
Expand Down
153 changes: 153 additions & 0 deletions web/resources/js/memberships-chart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import ApexCharts from "apexcharts";
import type { ApexOptions } from "apexcharts";
import * as bootstrap from "bootstrap";

const HALF_YEAR = 182 * 24 * 60 * 60 * 1000
const INFINITY = Date.parse("3000-01-01T00:00:00Z")
const fmt = (ms: number): string => {
let d = new Date(ms);
d.setSeconds(0, 0)
// let str = d.toISOString().slice(0, -5)
// return `<code>${str}Z</code>`
// "de" because ISO is unreadable in this context (too information dense) and "en" is just cursed
let str = d.toLocaleString("de")
return `<code>${str}</code>`
};

function demandElementById<T extends HTMLElement>(id: string): T {
const el = document.getElementById(id)
if (el === null) {
throw new Error(`Element #${id} not found`)
}
return el as T
}
const options_: ApexOptions = {
chart: {
height: "200px",
type: 'rangeBar',
events: {
// see https://apexcharts.com/docs/options/chart/events/
click: (_event, _chartContext, w) => {
console.log(w);
let { seriesIndex, dataPointIndex } = w;
let data = w.config.series[seriesIndex].data[dataPointIndex] as Data;

// console.log(data);
const ID = "group-detail-modal"
demandElementById("group-detail-title").innerHTML = `Edit membership #${data.id.toString()}`;

let { url_edit: urlEdit, url_end: urlEnd } = data.orig_data
// console.log(actions)
let elTerm = demandElementById<HTMLAnchorElement>("group-detail-terminate")
let elEd = demandElementById<HTMLAnchorElement>("group-detail-edit")
elEd.href = urlEdit
console.log(urlEnd)

if (urlEnd !== null) {
elTerm.ariaHidden = "false"
elTerm.classList.remove("d-none")
elTerm.href = urlEnd
} else {
elTerm.ariaHidden = "true"
elTerm.classList.add("d-none")
}
const modal = new bootstrap.Modal(`#${ID}`, {});
console.log(modal)
modal.show();
},
},
},
plotOptions: {
bar: {
horizontal: true,
rangeBarGroupRows: true,
}
},
xaxis: {
type: 'datetime',
max: new Date().getTime() + HALF_YEAR,
tooltip: {enabled: true},
},
annotations: {
xaxis: [
// We could add lots of lines here (tasks, membership start, …).
{ x: new Date().getTime(), label: { text: "today" }, },
]
},
stroke: {
show: true,
curve: 'straight',
lineCap: 'butt',
colors: undefined,
width: 2, // this is important: otherwise we might not see e.g. small blockages
dashArray: 0,
},
tooltip: {
custom: ({ctx, series, seriesIndex, dataPointIndex, w}) => {
let data = w.config.series[seriesIndex].data[dataPointIndex] as Data
let [since, until] = data.y
let range_desc = data.ends_unbounded
? `since <i class="fa-solid fa-right-from-bracket"></i> ${fmt(since)}`
: `from <i class="fa-solid fa-right-from-bracket"></i> ${fmt(since)}
<br> to <i class="fa-solid fa-right-to-bracket"></i> ${fmt(until)}`
return `
<div class="card">
<div class="card-body p-2">
<div class="fw-bold fs-5 mb-1">${data.x}</div>
<p class="card-text fs-6">${range_desc}</p>
<!-- <a href="#" class="btn btn-primary">Go somewhere</a> -->
</div>
`
}
},
};

document.addEventListener('DOMContentLoaded', () => {
const id = "memberships-timeline";
const el = document.getElementById(id);
if (el === null) {
console.error(`no element with id ${id} found`);
return;
}
fetch(el.dataset.url!)
.then(resp => resp.json())
.catch(console.error)
.then(j => {
const data = parseResponse(j);
console.log(data)
let chart = new ApexCharts(
el,
{
...options_,
series: [
{data},
],
}
)
chart.render()
})
})

type Data = {
x: string
y: [number, number]
begins_unbounded: boolean
ends_unbounded: boolean
id: number,
orig_data: any,
}
function parseResponse(j: any): Data[] {
// schema for `j`: see `user_show_groups_json`
return j.items?.map(mem => ({
x: mem.group_name,
y: [
new Date(mem.begins_at.timestamp * 1000).getTime(),
mem.ends_at.timestamp ? new Date(mem.ends_at.timestamp * 1000) : INFINITY,
],
begins_unbounded: mem.begins_at.timestamp == null,
ends_unbounded: mem.ends_at.timestamp == null,
id: mem.id,
orig_data: mem,
} as Data))
}

38 changes: 34 additions & 4 deletions web/templates/user/_user_show_groups.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,46 @@ <h3>Resultierende Properties</h3>
</div>
<br>


<ul class="nav nav-tabs">
<li class="nav-item"><a class="nav-link active" href="#memberships-active" data-bs-toggle="tab">Aktiv</a></li>
<li class="nav-item"><a class="nav-link" href="#memberships-all" data-bs-toggle="tab">Alle</a></li>
<li class="nav-item"><a class="nav-link active" href="#memberships-chart" data-bs-toggle="tab">
<i class="fa-solid fa-chart-gantt"></i> Chart
</a></li>
<li class="nav-item"><a class="nav-link" href="#memberships-active" data-bs-toggle="tab">
<i class="fa-solid fa-list-ul"></i> Aktiv
</a></li>
<li class="nav-item"><a class="nav-link" href="#memberships-all" data-bs-toggle="tab">
<i class="fa-solid fa-list-ul"></i> Alle
</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane fade in active show" id="memberships-active">
<div class="tab-pane fade in active show" id="memberships-chart">
<div id="memberships-timeline" data-url="{{ url_for(".user_show_groups_json", user_id=user.id) }}"></div>
</div>
<div class="tab-pane fade" id="memberships-active">
{{ membership_table_active.render("active-memberships") }}
</div>
<div class="tab-pane fade" id="memberships-all">
{{ membership_table_all.render("memberships")}}
</div>


<div id="group-detail-modal" class="modal fade" tabindex="-1"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 id="group-detail-title" class="modal-title">Edit membership</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<a type="button" id="group-detail-terminate" href="#" class="btn btn-outline-danger">
<i class="fa-solid fa-power-off"></i>Beenden
</a>
<a type="button" id="group-detail-edit" href="#" class="btn btn-primary">
<i class="fa-solid fa-pencil"></i>Bearbeiten
</a>
</div>
</div>
</div>
</div>
</div>
Loading

0 comments on commit 19b06bd

Please sign in to comment.