Major redesign #73
|
@ -1390,6 +1390,10 @@ input:checked + .toggle-bg {
|
|||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.me-2 {
|
||||
margin-inline-end: 0.5rem;
|
||||
}
|
||||
|
||||
.ml-1 {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
@ -1402,6 +1406,10 @@ input:checked + .toggle-bg {
|
|||
margin-inline-start: 0px;
|
||||
}
|
||||
|
||||
.ms-2 {
|
||||
margin-inline-start: 0.5rem;
|
||||
}
|
||||
|
||||
.ms-2\.5 {
|
||||
margin-inline-start: 0.625rem;
|
||||
}
|
||||
|
@ -1414,6 +1422,14 @@ input:checked + .toggle-bg {
|
|||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mb-6 {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
@ -1826,6 +1842,11 @@ input:checked + .toggle-bg {
|
|||
border-color: rgb(220 215 254 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(225 239 254 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-blue-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(26 86 219 / var(--tw-bg-opacity));
|
||||
|
@ -1900,6 +1921,11 @@ input:checked + .toggle-bg {
|
|||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.px-2\.5 {
|
||||
padding-left: 0.625rem;
|
||||
padding-right: 0.625rem;
|
||||
}
|
||||
|
||||
.px-3 {
|
||||
padding-left: 0.75rem;
|
||||
padding-right: 0.75rem;
|
||||
|
@ -1920,6 +1946,11 @@ input:checked + .toggle-bg {
|
|||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.py-0\.5 {
|
||||
padding-top: 0.125rem;
|
||||
padding-bottom: 0.125rem;
|
||||
}
|
||||
|
||||
.py-1 {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
|
@ -1961,6 +1992,10 @@ input:checked + .toggle-bg {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.align-top {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
@ -2026,6 +2061,10 @@ input:checked + .toggle-bg {
|
|||
font-weight: 700;
|
||||
}
|
||||
|
||||
.font-extrabold {
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.font-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
@ -2050,15 +2089,28 @@ input:checked + .toggle-bg {
|
|||
line-height: 2.25rem;
|
||||
}
|
||||
|
||||
.leading-none {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.leading-tight {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.tracking-tight {
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.text-blue-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(28 100 242 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-blue-800 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(30 66 159 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-300 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(209 213 219 / var(--tw-text-opacity));
|
||||
|
@ -2718,6 +2770,11 @@ textarea:disabled:is(.dark *) {
|
|||
border-color: transparent;
|
||||
}
|
||||
|
||||
.dark\:bg-blue-200:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(195 221 253 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-blue-600:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(28 100 242 / var(--tw-bg-opacity));
|
||||
|
@ -2761,6 +2818,11 @@ textarea:disabled:is(.dark *) {
|
|||
color: rgb(63 131 248 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-blue-800:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(30 66 159 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.dark\:text-gray-200:is(.dark *) {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(229 231 235 / var(--tw-text-opacity));
|
||||
|
@ -2876,6 +2938,11 @@ textarea:disabled:is(.dark *) {
|
|||
--tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-blue-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(30 66 159 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-gray-600:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity));
|
||||
|
@ -2887,10 +2954,6 @@ textarea:disabled:is(.dark *) {
|
|||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.sm\:table-cell {
|
||||
display: table-cell;
|
||||
}
|
||||
|
@ -2916,10 +2979,6 @@ textarea:disabled:is(.dark *) {
|
|||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.sm\:pl-12 {
|
||||
padding-left: 3rem;
|
||||
}
|
||||
|
||||
.sm\:pl-2 {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
@ -2928,10 +2987,6 @@ textarea:disabled:is(.dark *) {
|
|||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.sm\:pl-6 {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.sm\:decoration-2 {
|
||||
text-decoration-thickness: 2px;
|
||||
}
|
||||
|
@ -3003,6 +3058,11 @@ textarea:disabled:is(.dark *) {
|
|||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.md\:text-5xl {
|
||||
font-size: 3rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.md\:text-blue-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(26 86 219 / var(--tw-text-opacity));
|
||||
|
@ -3053,6 +3113,11 @@ textarea:disabled:is(.dark *) {
|
|||
.lg\:max-w-lg {
|
||||
max-width: 32rem;
|
||||
}
|
||||
|
||||
.lg\:text-6xl {
|
||||
font-size: 3.75rem;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1536px) {
|
||||
|
@ -3070,6 +3135,10 @@ textarea:disabled:is(.dark *) {
|
|||
--tw-space-x-reverse: 1;
|
||||
}
|
||||
|
||||
.rtl\:text-left:where([dir="rtl"], [dir="rtl"] *) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) {
|
||||
text-align: right;
|
||||
}
|
||||
|
|
|
@ -7,3 +7,4 @@ components:
|
|||
simple_table: "components/simple_table.html"
|
||||
button_group_sm: "components/button_group_sm.html"
|
||||
button_group_button_sm: "components/button_group_button_sm.html"
|
||||
h1: "components/h1.html"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<h1 class="{% if badge %}flex items-center {% endif %}mb-4 text-3xl font-extrabold leading-none tracking-tight text-gray-900 dark:text-white">
|
||||
{{ children }}
|
||||
{% if badge %}
|
||||
<span class="bg-blue-100 text-blue-800 text-2xl font-semibold me-2 px-2.5 py-0.5 rounded dark:bg-blue-200 dark:text-blue-800 ms-2">
|
||||
{{ badge }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</h1>
|
|
@ -13,7 +13,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% if page_obj %}
|
||||
{% if page_obj and elided_page_range %}
|
||||
<nav class="flex items-center flex-column md:flex-row justify-between px-6 py-4"
|
||||
aria-label="Table navigation">
|
||||
<span class="text-sm font-normal text-gray-500 dark:text-gray-400 mb-4 md:mb-0 block w-full md:inline md:w-auto">Showing <span class="font-semibold text-gray-900 dark:text-white">{{ page_obj.start_index }}</span>—<span class="font-semibold text-gray-900 dark:text-white">{{ page_obj.end_index }}</span> of <span class="font-semibold text-gray-900 dark:text-white">{{ page_obj.paginator.count }}</span></span>
|
||||
|
|
|
@ -90,98 +90,16 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-3xl mt-4 mb-1 font-condensed">
|
||||
Editions <span class="dark:text-slate-500">({{ edition_count }})</span> and Purchases <span class="dark:text-slate-500">({{ purchase_count }})</span>
|
||||
</h1>
|
||||
<ul>
|
||||
{% for edition in editions %}
|
||||
<li class="sm:pl-2 flex items-center">
|
||||
{{ edition.name }} ({{ edition.platform }}, {{ edition.year_released }})
|
||||
{% if edition.wikidata %}
|
||||
<span class="hidden sm:inline">
|
||||
<a href="https://www.wikidata.org/wiki/{{ edition.wikidata }}">
|
||||
<img class="inline mx-2 w-6"
|
||||
width="48"
|
||||
height="48"
|
||||
alt="Wikidata Icon"
|
||||
src="{% static 'icons/wikidata.png' %}" />
|
||||
</a>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% url 'edit_edition' edition.id as edit_url %}
|
||||
{% include "components/edit_button.html" with edit_url=edit_url %}
|
||||
</li>
|
||||
<ul>
|
||||
{% for purchase in edition.game_purchases %}
|
||||
<li class="sm:pl-6 flex items-center {% if purchase.date_refunded %}text-red-600{% endif %}">
|
||||
{{ purchase.get_ownership_type_display }}, {{ purchase.date_purchased | date:"Y" }}
|
||||
{% if purchase.price != 0 %}({{ purchase.price }} {{ purchase.price_currency }}){% endif %}
|
||||
{% url 'edit_purchase' purchase.id as edit_url %}
|
||||
{% include "components/edit_button.html" with edit_url=edit_url %}
|
||||
</li>
|
||||
<ul>
|
||||
{% for related_purchase in purchase.nongame_related_purchases %}
|
||||
<li class="sm:pl-12 flex items-center">
|
||||
{{ related_purchase.name }} ({{ related_purchase.get_type_display }}, {{ purchase.platform }}, {{ related_purchase.date_purchased | date:"Y" }}, {{ related_purchase.price }} {{ related_purchase.price_currency }})
|
||||
{% url 'edit_purchase' related_purchase.id as edit_url %}
|
||||
{% include "components/edit_button.html" with edit_url=edit_url %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<h1 class="text-3xl mt-4 mb-1 flex gap-2 items-center font-condensed">
|
||||
Sessions
|
||||
<span class="dark:text-slate-500" id="session-count">({{ session_count }})</span>
|
||||
{% if latest_session_id %}
|
||||
{% url 'view_game_start_session_from_session' latest_session_id as add_session_link %}
|
||||
<a class="truncate max-w-xs py-1 px-2 text-xs bg-green-600 hover:bg-green-700 focus:ring-green-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-sm"
|
||||
title="Start new session"
|
||||
href="{{ add_session_link }}"
|
||||
hx-get="{{ add_session_link }}"
|
||||
hx-vals="js:{session_count:getSessionCount()}"
|
||||
hx-target="#session-list"
|
||||
hx-swap="afterbegin">New</a>
|
||||
{% endif %}
|
||||
and Notes <span class="dark:text-slate-500">({{ sessions_with_notes_count }})</span>
|
||||
</h1>
|
||||
<ul id="session-list">
|
||||
{% for session in sessions %}
|
||||
{% partialdef session-info inline=True %}
|
||||
<li class="sm:pl-2 mt-4 mb-2 dark:text-slate-400 flex items-center space-x-1">
|
||||
{{ session.timestamp_start | date:"d/m/Y H:i" }}
|
||||
{% if session.timestamp_end %}-{{ session.timestamp_end | date:"H:i" }}{% endif %}
|
||||
({{ session.device.get_type_display | default:"Unknown" }}, {{ session.duration_formatted }})
|
||||
{% url 'edit_session' session.id as edit_url %}
|
||||
{% include "components/edit_button.html" with edit_url=edit_url %}
|
||||
{% if not session.timestamp_end %}
|
||||
{% url 'view_game_end_session' session.id as end_session_url %}
|
||||
<a class="flex bg-green-600 rounded-full px-2 w-7 h-4 text-white justify-center items-center"
|
||||
href="{{ end_session_url }}"
|
||||
hx-get="{{ end_session_url }}"
|
||||
hx-target="closest li"
|
||||
hx-swap="outerHTML"
|
||||
hx-vals="js:{session_count:getSessionCount()}"
|
||||
hx-indicator="#indicator">
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff"
|
||||
class="h-3"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 24 24">
|
||||
<path d="M 12 2 C 6.486 2 2 6.486 2 12 C 2 17.514 6.486 22 12 22 C 17.514 22 22 17.514 22 12 C 22 10.874 21.803984 9.7942031 21.458984 8.7832031 L 19.839844 10.402344 C 19.944844 10.918344 20 11.453 20 12 C 20 16.411 16.411 20 12 20 C 7.589 20 4 16.411 4 12 C 4 7.589 7.589 4 12 4 C 13.633 4 15.151922 4.4938906 16.419922 5.3378906 L 17.851562 3.90625 C 16.203562 2.71225 14.185 2 12 2 z M 21.292969 3.2929688 L 11 13.585938 L 7.7070312 10.292969 L 6.2929688 11.707031 L 11 16.414062 L 22.707031 4.7070312 L 21.292969 3.2929688 z">
|
||||
</path>
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="sm:pl-4 markdown-content">{{ session.note|markdown }}</li>
|
||||
<div class="hidden" hx-swap-oob="innerHTML:#session-count">({{ session_count }})</div>
|
||||
{% endpartialdef %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% #h1 badge=edition_count %}Editions{% /h1 %}
|
||||
<div class="mb-6">{% simple_table rows=edition_data.rows columns=edition_data.columns %}</div>
|
||||
<div class="mb-6">
|
||||
{% #h1 badge=purchase_count %}Purchases{% /h1 %}
|
||||
{% simple_table rows=purchase_data.rows columns=purchase_data.columns %}
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
{% #h1 badge=session_count %}Sessions{% /h1 %}
|
||||
{% simple_table rows=session_data.rows columns=session_data.columns page_obj=session_page_obj elided_page_range=session_elided_page_range %}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function getSessionCount() {
|
||||
|
|
|
@ -2,16 +2,24 @@ from typing import Any
|
|||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models import Prefetch
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
|
||||
from common.time import format_duration
|
||||
from common.utils import safe_division, safe_getattr, truncate_with_popover
|
||||
from common.utils import safe_division, truncate_with_popover
|
||||
from games.forms import GameForm
|
||||
from games.models import Game, Purchase, Session
|
||||
from games.views.general import dateformat, use_custom_redirect
|
||||
from games.models import Edition, Game, Purchase, Session
|
||||
from games.views.general import (
|
||||
dateformat,
|
||||
datetimeformat,
|
||||
durationformat,
|
||||
durationformat_manual,
|
||||
timeformat,
|
||||
use_custom_redirect,
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -143,6 +151,8 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
|||
.order_by("year_released")
|
||||
)
|
||||
|
||||
purchases = Purchase.objects.filter(edition__game=game).order_by("date_purchased")
|
||||
|
||||
sessions = Session.objects.prefetch_related("device").filter(
|
||||
purchase__edition__game=game
|
||||
)
|
||||
|
@ -169,7 +179,114 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
|||
total_hours_without_manual = float(
|
||||
format_duration(sessions.calculated_duration_unformatted(), "%2.1H")
|
||||
)
|
||||
context = {
|
||||
|
||||
edition_data: dict[str, Any] = {
|
||||
"columns": [
|
||||
"Name",
|
||||
"Platform",
|
||||
"Year Released",
|
||||
"Actions",
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
edition.name,
|
||||
edition.platform,
|
||||
edition.year_released,
|
||||
render_to_string(
|
||||
"components/button_group_sm.html",
|
||||
{
|
||||
"buttons": [
|
||||
{
|
||||
"href": reverse("edit_edition", args=[edition.pk]),
|
||||
"text": "Edit",
|
||||
},
|
||||
{
|
||||
"href": reverse("delete_edition", args=[edition.pk]),
|
||||
"text": "Delete",
|
||||
"color": "red",
|
||||
},
|
||||
]
|
||||
},
|
||||
),
|
||||
]
|
||||
for edition in editions
|
||||
],
|
||||
}
|
||||
|
||||
purchase_data: dict[str, Any] = {
|
||||
"columns": ["Name", "Type", "Price", "Actions"],
|
||||
"rows": [
|
||||
[
|
||||
purchase.name if purchase.name else purchase.edition.name,
|
||||
purchase.get_type_display(),
|
||||
f"{purchase.price} {purchase.price_currency}",
|
||||
render_to_string(
|
||||
"components/button_group_sm.html",
|
||||
{
|
||||
"buttons": [
|
||||
{
|
||||
"href": reverse("edit_purchase", args=[purchase.pk]),
|
||||
"text": "Edit",
|
||||
},
|
||||
{
|
||||
"href": reverse("delete_purchase", args=[purchase.pk]),
|
||||
"text": "Delete",
|
||||
"color": "red",
|
||||
},
|
||||
]
|
||||
},
|
||||
),
|
||||
]
|
||||
for purchase in purchases
|
||||
],
|
||||
}
|
||||
|
||||
sessions_all = Session.objects.filter(purchase__edition__game=game).order_by(
|
||||
"-timestamp_start"
|
||||
)
|
||||
session_count = sessions_all.count()
|
||||
session_paginator = Paginator(sessions_all, 5)
|
||||
page_number = request.GET.get("page", 1)
|
||||
session_page_obj = session_paginator.get_page(page_number)
|
||||
sessions = session_page_obj.object_list
|
||||
|
||||
session_data: dict[str, Any] = {
|
||||
"columns": ["Date", "Duration", "Duration (manual)", "Actions"],
|
||||
"rows": [
|
||||
[
|
||||
f"{session.timestamp_start.strftime(datetimeformat)}{f" — {session.timestamp_end.strftime(timeformat)}" if session.timestamp_end else ""}",
|
||||
(
|
||||
format_duration(session.duration_calculated, durationformat)
|
||||
if session.duration_calculated
|
||||
else "-"
|
||||
),
|
||||
(
|
||||
format_duration(session.duration_manual, durationformat_manual)
|
||||
if session.duration_manual
|
||||
else "-"
|
||||
),
|
||||
render_to_string(
|
||||
"components/button_group_sm.html",
|
||||
{
|
||||
"buttons": [
|
||||
{
|
||||
"href": reverse("edit_session", args=[session.pk]),
|
||||
"text": "Edit",
|
||||
},
|
||||
{
|
||||
"href": reverse("delete_session", args=[session.pk]),
|
||||
"text": "Delete",
|
||||
"color": "red",
|
||||
},
|
||||
]
|
||||
},
|
||||
),
|
||||
]
|
||||
for session in sessions
|
||||
],
|
||||
}
|
||||
|
||||
context: dict[str, Any] = {
|
||||
"edition_count": editions.count(),
|
||||
"editions": editions,
|
||||
"game": game,
|
||||
|
@ -182,11 +299,20 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
|||
1,
|
||||
),
|
||||
"session_count": session_count,
|
||||
"sessions_with_notes_count": sessions.exclude(note="").count(),
|
||||
"sessions": sessions.order_by("-timestamp_start"),
|
||||
"sessions": sessions,
|
||||
"title": f"Game Overview - {game.name}",
|
||||
"hours_sum": total_hours,
|
||||
"latest_session_id": safe_getattr(latest_session, "pk"),
|
||||
"edition_data": edition_data,
|
||||
"purchase_data": purchase_data,
|
||||
"session_data": session_data,
|
||||
"session_page_obj": session_page_obj,
|
||||
"session_elided_page_range": (
|
||||
session_page_obj.paginator.get_elided_page_range(
|
||||
page_number, on_each_side=1, on_ends=1
|
||||
)
|
||||
if session_page_obj and session_count > 5
|
||||
else None
|
||||
),
|
||||
}
|
||||
|
||||
request.session["return_path"] = request.path
|
||||
|
|
Loading…
Reference in New Issue