diff --git a/games/api.py b/games/api.py
index a2fdec2..28f72c6 100644
--- a/games/api.py
+++ b/games/api.py
@@ -5,7 +5,7 @@ from django.shortcuts import get_object_or_404
from django.utils.timezone import now as django_timezone_now
from ninja import Field, ModelSchema, NinjaAPI, Router, Schema
-from games.models import Game, PlayEvent
+from games.models import Game, PlayEvent, Session
api = NinjaAPI()
playevent_router = Router()
@@ -93,3 +93,20 @@ def delete_playevent(request, playevent_id: int):
api.add_router("/playevent", playevent_router)
api.add_router("/games", game_router)
+session_router = Router()
+
+
+class SessionDeviceUpdate(Schema):
+ device_id: int
+
+
+@session_router.patch("/{session_id}/device", response={204: None})
+def partial_update_session_device(request, session_id: int, payload: SessionDeviceUpdate):
+ session = get_object_or_404(Session, id=session_id)
+ session.device_id = payload.device_id
+ session.save()
+ return 204, None
+
+
+api.add_router("/session", session_router)
+
diff --git a/games/static/base.css b/games/static/base.css
index c72d81d..74f4fd9 100644
--- a/games/static/base.css
+++ b/games/static/base.css
@@ -2490,9 +2490,15 @@
.text-gray-900 {
color: var(--color-gray-900);
}
+ .text-green-600 {
+ color: var(--color-green-600);
+ }
.text-heading {
color: var(--color-heading);
}
+ .text-red-600 {
+ color: var(--color-red-600);
+ }
.text-slate-300 {
color: var(--color-slate-300);
}
@@ -3333,6 +3339,16 @@
color: var(--color-gray-600);
}
}
+ .dark\:text-green-400 {
+ &:is(.dark *) {
+ color: var(--color-green-400);
+ }
+ }
+ .dark\:text-red-400 {
+ &:is(.dark *) {
+ color: var(--color-red-400);
+ }
+ }
.dark\:text-slate-300 {
&:is(.dark *) {
color: var(--color-slate-300);
diff --git a/games/templates/cotton/table_row.html b/games/templates/cotton/table_row.html
index 1a91831..ffd07ab 100644
--- a/games/templates/cotton/table_row.html
+++ b/games/templates/cotton/table_row.html
@@ -1,11 +1,26 @@
-
+
{% if slot %}
{{ slot }}
+ {% elif data.row_id %}
+ {% for td in data.cell_data %}
+ {% if forloop.first %}
+ | {{ td }} |
+ {% else %}
+
+ {{ td }}
+
+ {% endif %}
+ {% endfor %}
{% else %}
{% for td in data %}
{% if forloop.first %}
- {{ td }} |
+ {{ td }} |
{% else %}
{{ td }}
diff --git a/games/templates/partials/sessiondevice_selector.html b/games/templates/partials/sessiondevice_selector.html
new file mode 100644
index 0000000..683e7ee
--- /dev/null
+++ b/games/templates/partials/sessiondevice_selector.html
@@ -0,0 +1,56 @@
+
diff --git a/games/views/session.py b/games/views/session.py
index 8b88c63..9ffb2d4 100644
--- a/games/views/session.py
+++ b/games/views/session.py
@@ -25,7 +25,7 @@ from common.time import (
)
from common.utils import truncate
from games.forms import SessionForm
-from games.models import Game, Session
+from games.models import Device, Game, Session
@login_required
@@ -34,6 +34,7 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
page_number = request.GET.get("page", 1)
limit = request.GET.get("limit", 10)
sessions = Session.objects.order_by("-timestamp_start", "created_at")
+ device_list = Device.objects.order_by("name")
search_string = request.GET.get("search_string", search_string)
if search_string != "":
sessions = sessions.filter(
@@ -123,51 +124,66 @@ def list_sessions(request: HttpRequest, search_string: str = "") -> HttpResponse
"Actions",
],
"rows": [
- [
- NameWithIcon(session_id=session.pk),
- f"{local_strftime(session.timestamp_start)}{f' — {local_strftime(session.timestamp_end, timeformat)}' if session.timestamp_end else ''}",
- session.duration_formatted_with_mark,
- session.device,
- session.created_at.strftime(dateformat),
- render_to_string(
- "cotton/button_group.html",
- {
- "buttons": [
- {
- "href": reverse(
- "list_sessions_end_session", args=[session.pk]
- ),
- "slot": Icon("end"),
- "title": "Finish session now",
- "color": "green",
- "hover": "green",
- }
- if session.timestamp_end is None
- # this only works without leaving an empty
- # a element and wrong rounding of button edges
- # because we check if button.href is not None
- # in the button group component
- else {},
- {
- "href": reverse("edit_session", args=[session.pk]),
- "slot": Icon("edit"),
- "title": "Edit",
- # "color": "gray",
- "hover": "green",
- },
- {
- "href": reverse(
- "delete_session", args=[session.pk]
- ),
- "slot": Icon("delete"),
- "title": "Delete",
- "color": "red",
- "hover": "red",
- },
- ]
- },
- ),
- ]
+ {
+ "row_id": f"session-row-{session.pk}",
+ "hx_trigger": "device-changed from:body",
+ "hx_get": "",
+ "hx_select": f"#session-row-{session.pk}",
+ "hx_swap": "outerHTML",
+ "cell_data": [
+ NameWithIcon(session_id=session.pk),
+ f"{local_strftime(session.timestamp_start)}{f' — {local_strftime(session.timestamp_end, timeformat)}' if session.timestamp_end else ''}",
+ session.duration_formatted_with_mark,
+ render_to_string(
+ "partials/sessiondevice_selector.html",
+ {
+ "session": session,
+ "session_device": session.device,
+ "session_devices": device_list,
+ },
+ request=request,
+ ),
+ session.created_at.strftime(dateformat),
+ render_to_string(
+ "cotton/button_group.html",
+ {
+ "buttons": [
+ {
+ "href": reverse(
+ "list_sessions_end_session", args=[session.pk]
+ ),
+ "slot": Icon("end"),
+ "title": "Finish session now",
+ "color": "green",
+ "hover": "green",
+ }
+ if session.timestamp_end is None
+ # this only works without leaving an empty
+ # a element and wrong rounding of button edges
+ # because we check if button.href is not None
+ # in the button group component
+ else {},
+ {
+ "href": reverse("edit_session", args=[session.pk]),
+ "slot": Icon("edit"),
+ "title": "Edit",
+ # "color": "gray",
+ "hover": "green",
+ },
+ {
+ "href": reverse(
+ "delete_session", args=[session.pk]
+ ),
+ "slot": Icon("delete"),
+ "title": "Delete",
+ "color": "red",
+ "hover": "red",
+ },
+ ]
+ },
+ ),
+ ],
+ }
for session in sessions
],
},