who-when-where/frontend/view.html

395 lines
14 KiB
HTML
Raw Permalink Normal View History

2024-01-02 22:49:27 +01:00
<!--
view.html: "View" page for who-when-where
For each users listed under the view, display:
* Name and profile picture
* Location (emoji)
* Availability (green-yellow-red)
for a certain time period (customisable via the interface).
Also displays a summary for each day, per location.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3W - Who When Where?</title>
<link rel="stylesheet" href="main.css">
<style>
table#view_table {
border-collapse: collapse;
}
table#view_table th, table#view_table td {
padding: 1em 1.2em;
font-weight: normal;
}
table#view_table th:first-child {
text-align: right;
}
table#view_table thead tr, table#view_table tfoot tr {
background-color: var(--colour_primary);
color: var(--colour_primary_text);
text-align: left;
}
table#view_table thead th {
text-align: center;
vertical-align: bottom;
2024-01-02 22:49:27 +01:00
}
table#view_table thead th:first-child {
font-weight: bold;
}
table#view_table thead th.me {
font-weight: bold;
}
table#view_table thead th img {
display: block;
margin: auto;
margin-bottom: 1em;
border-radius: 50%;
max-width: 4em;
max-height: 4em;
}
2024-01-02 22:49:27 +01:00
table#view_table tbody tr {
border-bottom: 1px solid #dddddd;
}
table#view_table tbody th {
font-weight: bold;
text-align: right;
}
table#view_table tbody th span {
font-size: 0.8em;
font-weight: normal;
display: inline-block;
margin-left: 0.33em;
2024-01-02 22:49:27 +01:00
}
table#view_table tbody tr:nth-of-type(odd) {
background-color: var(--colour_lightgrey);
}
table#view_table tbody tr:nth-of-type(even) {
background-color: #ffffff;
}
table#view_table tbody td.me {
cursor: pointer;
}
table#view_table tbody td {
text-align: center;
}
table#view_table td .where {
font-size: 2.5em;
position: relative;
}
table#view_table td:has(.modifier) {
padding-right: 1.55em;
}
i.modifier {
font-style: normal;
font-size: 0.33em;
width: 0.33em;
position: absolute;
right: 0.33em;
}
i.modifier-top {
top: -0em;
}
i.modifier-bottom {
bottom: -0em;
}
i.modifier-cross::before {
display: block;
width: 1em;
position: absolute;
top: -0.25em;
text-align: center;
font-size: 1.5em;
content: "✗";
color: rgba(255, 0, 0, 0.7);
}
table#view_table tfoot {
font-style: italic;
}
</style>
</head>
<body>
<table id="view_table">
<thead>
<tr id="view_table_thead_headers">
<th id="view_table_thead_name"></th>
2024-01-02 22:49:27 +01:00
<!-- For-each group member -->
</tr>
</thead>
<tbody>
<!-- For-each date -->
</tbody>
<tfoot>
<tr>
<th id="view_table_tfoot_total"></th>
2024-01-02 22:49:27 +01:00
<!-- Colspan = number of members -->
<th id="view_table_tfoot_filler"></th>
2024-01-02 22:49:27 +01:00
</tr>
</tfoot>
</table>
<p>
So far, this group has planned between
<span id="view_first_filled_date"></span>
and
<span id="view_last_filled_date"></span>.
2024-01-02 22:49:27 +01:00
</p>
<p>
<a href="index.html">Take me home!</a>
</p>
<template id="row_date">
<tr>
<th></th>
<!-- For-each group member = availability -->
</tr>
</template>
<template id="cell_availability">
<td>
<span class="where"></span>
</td>
</template>
<template id="modifier_cannot_host">
<i class="modifier modifier-top modifier-cross" title="Cannot host">🏠</i>
</template>
<template id="modifier_cannot_travel">
<i class="modifier modifier-bottom modifier-cross" title="Cannot travel">🚅</i>
</template>
<script>
var userUid = 1;
var viewData = {
view: {
name: "Le groupe des abeilles",
numberOfGroupMembers: 3,
firstFilledDate: "2023-05-06",
lastFilledDate: "2025-01-31"
},
members: [
{
uid: 1,
displayName: "Louis",
avatar: "https://static.david-david-studio.com/image/24511.jpg"
},
{
uid: 2,
displayName: "Mathilde",
avatar: "https://tinder.com/static/tinder.png"
},
{
uid: 3,
displayName: "Louis",
avatar: "https://www.aprifel.com/wp-content/uploads/2019/02/potiron.jpg"
}
],
dates: [
{
date: "2024-08-01",
membersAvailability: [
{
memberUid: 1,
location: "🇸🇪",
canHost: true,
canTravel: true
},
{
memberUid: 2,
location: "🗼",
canHost: true,
canTravel: true
},
{
memberUid: 3,
location: "🥨",
canHost: true,
canTravel: true
}
]
},
{
date: "2024-08-02",
membersAvailability: [
{
memberUid: 1,
location: "🗼",
canHost: false,
canTravel: true
},
{
memberUid: 2,
location: "🗼",
canHost: true,
canTravel: true
},
{
memberUid: 3,
location: "🥨",
canHost: true,
canTravel: false
}
]
}
]
};
function data2dom(data) {
// Retrieve common DOM elements
var headerRow = document.getElementById("view_table_thead_headers");
var tbody = document.getElementById("view_table").querySelector("tbody");
// Prepared data
var numberOfDisplayedMembers = data.members.length;
// Static: update header
document.getElementById("view_table_thead_name").textContent = data.view.name;
// Static: update footer
let strViewMembersCount = data.view.numberOfGroupMembers + " group member";
if(data.view.numberOfGroupMembers > 1) strViewMembersCount += "s";
document.getElementById("view_table_tfoot_total").textContent = strViewMembersCount;
document.getElementById("view_table_tfoot_filler").setAttribute("colspan", numberOfDisplayedMembers);
// Static: update below table
document.getElementById("view_first_filled_date").textContent = data.view.firstFilledDate;
document.getElementById("view_last_filled_date").textContent = data.view.lastFilledDate;
// For each member
data.members.forEach(element => {
let newCell = document.createElement("th");
let displayName;
if(element.uid == userUid) {
displayName = "Me (" + element.displayName + ")";
newCell.classList.add("me");
}
else {
displayName = element.displayName;
}
if(element.avatar) {
let newAvatar = document.createElement("img");
newAvatar.setAttribute("src", element.avatar);
newAvatar.setAttribute("alt", "Photo for " + element.displayName);
newCell.appendChild(newAvatar);
}
let newTextData = document.createTextNode(displayName);
newCell.appendChild(newTextData);
headerRow.appendChild(newCell);
});
// For each date
const rowDateTemplate = document.getElementById("row_date");
const cellAvailabilityTemplate = document.getElementById("cell_availability");
const modifierCannotHostTemplate = document.getElementById("modifier_cannot_host");
const modifierCannotTravelTemplate = document.getElementById("modifier_cannot_travel");
data.dates.forEach(element => {
let clonedRow = rowDateTemplate.content.cloneNode(true);
let header = clonedRow.querySelector("th");
let headerName = document.createTextNode(element.date);
header.appendChild(headerName);
let locations = [];
element.membersAvailability.forEach(availability => {
// Creating the DOM
let clonedCell = cellAvailabilityTemplate.content.cloneNode(true);
if(userUid == availability.memberUid) {
clonedCell.children[0].classList.add("me");
}
let whereSpan = clonedCell.querySelector("span.where");
let locationText = document.createTextNode(availability.location);
whereSpan.appendChild(locationText);
if(!availability.canHost) {
let clonedModifier = modifierCannotHostTemplate.content.cloneNode(true);
whereSpan.appendChild(clonedModifier);
}
if(!availability.canTravel) {
let clonedModifier = modifierCannotTravelTemplate.content.cloneNode(true);
whereSpan.appendChild(clonedModifier);
}
clonedRow.children[0].appendChild(clonedCell);
// Aggregating data per location, for totals
let existingLocations = locations.filter(e => e.location === availability.location);
if(existingLocations.length < 1) {
locations.push({
location: availability.location,
membersAvailable: 0,
hostsAvailable: 0,
travellersAvailable: 0
});
}
});
// Update other locations if member can travel
element.membersAvailability.forEach(availability => {
locations.forEach(existingLocation => {
if(existingLocation.location == availability.location) {
existingLocation.membersAvailable++;
if(availability.canHost) {
existingLocation.hostsAvailable++;
}
}
else if(availability.canTravel) {
existingLocation.membersAvailable++;
existingLocation.travellersAvailable++;
}
});
});
if(locations.length > 0) {
header.appendChild(document.createElement("br"));
}
locations.sort((a, b) => {
if(a.membersAvailable > b.membersAvailable) {
return -1;
}
else if(a.membersAvailable < b.membersAvailable) {
return 1;
}
return 0;
}).forEach(location => {
let spanLocation = document.createElement("span");
spanLocation.textContent = location.location + location.membersAvailable;
let title = [location.location + ": "];
if(location.hostsAvailable > 0) {
title.push(location.hostsAvailable + "🏠");
}
if(location.travellersAvailable > 0) {
title.push(location.travellersAvailable + "🚅");
}
let neitherHostNorTraveller = location.membersAvailable - location.hostsAvailable - location.travellersAvailable;
if(neitherHostNorTraveller > 0) {
title.push(neitherHostNorTraveller);
}
spanLocation.setAttribute("title", title.join(" "));
header.appendChild(spanLocation);
});
tbody.appendChild(clonedRow);
});
}
data2dom(viewData);
</script>
2024-01-02 22:49:27 +01:00
</body>
</html>