Fix dropdown and formatting
This commit is contained in:
+16
-1
@@ -5,14 +5,19 @@ from django.shortcuts import get_object_or_404
|
|||||||
from django.utils.timezone import now as django_timezone_now
|
from django.utils.timezone import now as django_timezone_now
|
||||||
from ninja import Field, ModelSchema, NinjaAPI, Router, Schema
|
from ninja import Field, ModelSchema, NinjaAPI, Router, Schema
|
||||||
|
|
||||||
from games.models import PlayEvent
|
from games.models import Game, PlayEvent
|
||||||
|
|
||||||
api = NinjaAPI()
|
api = NinjaAPI()
|
||||||
playevent_router = Router()
|
playevent_router = Router()
|
||||||
|
game_router = Router()
|
||||||
|
|
||||||
NOW_FACTORY = django_timezone_now
|
NOW_FACTORY = django_timezone_now
|
||||||
|
|
||||||
|
|
||||||
|
class GameStatusUpdate(Schema):
|
||||||
|
status: str
|
||||||
|
|
||||||
|
|
||||||
class PlayEventIn(Schema):
|
class PlayEventIn(Schema):
|
||||||
game_id: int
|
game_id: int
|
||||||
started: date | None = None
|
started: date | None = None
|
||||||
@@ -44,6 +49,14 @@ class PlayEventOut(Schema):
|
|||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
@game_router.patch("/{game_id}/status", response={204: None})
|
||||||
|
def partial_update_game(request, game_id: int, payload: GameStatusUpdate):
|
||||||
|
game = get_object_or_404(Game, id=game_id)
|
||||||
|
setattr(game, "status", payload.status)
|
||||||
|
game.save()
|
||||||
|
return 204, None
|
||||||
|
|
||||||
|
|
||||||
@playevent_router.get("/", response=List[PlayEventOut])
|
@playevent_router.get("/", response=List[PlayEventOut])
|
||||||
def list_playevents(request):
|
def list_playevents(request):
|
||||||
return PlayEvent.objects.all()
|
return PlayEvent.objects.all()
|
||||||
@@ -78,3 +91,5 @@ def delete_playevent(request, playevent_id: int):
|
|||||||
|
|
||||||
|
|
||||||
api.add_router("/playevent", playevent_router)
|
api.add_router("/playevent", playevent_router)
|
||||||
|
api.add_router("/games", game_router)
|
||||||
|
|
||||||
|
|||||||
+26
-20
@@ -1299,10 +1299,6 @@ input:checked + .toggle-bg {
|
|||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-left-3 {
|
|
||||||
left: -0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.-left-\[1px\] {
|
.-left-\[1px\] {
|
||||||
left: -1px;
|
left: -1px;
|
||||||
}
|
}
|
||||||
@@ -1343,10 +1339,6 @@ input:checked + .toggle-bg {
|
|||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.top-2 {
|
|
||||||
top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.top-3 {
|
.top-3 {
|
||||||
top: 0.75rem;
|
top: 0.75rem;
|
||||||
}
|
}
|
||||||
@@ -1355,6 +1347,10 @@ input:checked + .toggle-bg {
|
|||||||
top: 100%;
|
top: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-\[105\%\] {
|
||||||
|
top: 105%;
|
||||||
|
}
|
||||||
|
|
||||||
.z-10 {
|
.z-10 {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
@@ -1431,10 +1427,6 @@ input:checked + .toggle-bg {
|
|||||||
margin-inline-end: 0.5rem;
|
margin-inline-end: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ml-3 {
|
|
||||||
margin-left: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mr-4 {
|
.mr-4 {
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
@@ -1508,14 +1500,14 @@ input:checked + .toggle-bg {
|
|||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.h-2 {
|
|
||||||
height: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-2\.5 {
|
.h-2\.5 {
|
||||||
height: 0.625rem;
|
height: 0.625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-3 {
|
||||||
|
height: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-4 {
|
.h-4 {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
}
|
}
|
||||||
@@ -1548,10 +1540,6 @@ input:checked + .toggle-bg {
|
|||||||
width: 2.5rem;
|
width: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-2 {
|
|
||||||
width: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-2\.5 {
|
.w-2\.5 {
|
||||||
width: 0.625rem;
|
width: 0.625rem;
|
||||||
}
|
}
|
||||||
@@ -1708,6 +1696,10 @@ input:checked + .toggle-bg {
|
|||||||
grid-template-columns: repeat(7, minmax(0, 1fr));
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-row {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-col {
|
.flex-col {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -1864,6 +1856,11 @@ input:checked + .toggle-bg {
|
|||||||
border-end-start-radius: 0.5rem;
|
border-end-start-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rounded-t-none {
|
||||||
|
border-top-left-radius: 0px;
|
||||||
|
border-top-right-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.rounded-tl-none {
|
.rounded-tl-none {
|
||||||
border-top-left-radius: 0px;
|
border-top-left-radius: 0px;
|
||||||
}
|
}
|
||||||
@@ -3434,6 +3431,15 @@ div [type="submit"] {
|
|||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.\[\&_li\:first-of-type\]\:rounded-none li:first-of-type {
|
||||||
|
border-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.\[\&_li\:last-of-type\]\:rounded-t-none li:last-of-type {
|
||||||
|
border-top-left-radius: 0px;
|
||||||
|
border-top-right-radius: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
.\[\&_td\:last-child\]\:text-right td:last-child {
|
.\[\&_td\:last-child\]\:text-right td:last-child {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<span class="relative ml-3 {{class}}">
|
<span class="{% if display == 'flex' %}flex{% else %}inline-flex{% endif %} gap-2 items-center align-middle {{class}}">
|
||||||
<span class="rounded-xl w-2 h-2 absolute -left-3 top-2
|
<span class="rounded-xl w-3 h-3
|
||||||
{% if status == "u" %}
|
{% if status == "u" %}
|
||||||
bg-gray-500
|
bg-gray-500
|
||||||
{% elif status == "p" %}
|
{% elif status == "p" %}
|
||||||
|
|||||||
@@ -57,12 +57,67 @@
|
|||||||
<span class="uppercase">Original year</span>
|
<span class="uppercase">Original year</span>
|
||||||
<span class="text-slate-300">{{ game.original_year_released }}</span>
|
<span class="text-slate-300">{{ game.original_year_released }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center"
|
||||||
|
id="game-status-selector"
|
||||||
|
x-data="{
|
||||||
|
status: '{{ game.status }}',
|
||||||
|
status_display: '{{ game.get_status_display }}',
|
||||||
|
open: false,
|
||||||
|
saving: false,
|
||||||
|
setStatus(newStatus, newStatusDisplay) {
|
||||||
|
this.status = newStatus;
|
||||||
|
this.status_display = newStatusDisplay;
|
||||||
|
this.saving = true;
|
||||||
|
fetch(`/api/games/{{ game.id }}/status`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ status: newStatus })
|
||||||
|
}).finally(() => this.saving = false);
|
||||||
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
<span class="uppercase">Status</span>
|
<span class="uppercase">Status</span>
|
||||||
<c-gamestatus :status="game.status" class="text-slate-300">
|
|
||||||
{{ game.get_status_display }}
|
<div class="inline-flex rounded-md shadow-xs" role="group" @click.outside="open = false">
|
||||||
</c-gamestatus>
|
<button type="button" @click="open = !open" class="relative px-4 py-2 text-sm font-medium text-gray-900 bg-white border border-gray-200 rounded-lg hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-2 focus:ring-blue-700 focus:text-blue-700 dark:bg-gray-800 dark:border-gray-700 dark:text-white dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white align-middle">
|
||||||
{% if game.mastered %}👑{% endif %}
|
<span class="flex flex-row gap-4 justify-between items-center">
|
||||||
|
<span class="flex gap-2 items-center text-slate-300">
|
||||||
|
<span class="rounded-xl w-3 h-3"
|
||||||
|
:class="{
|
||||||
|
'bg-gray-500': status == 'u',
|
||||||
|
'bg-orange-400': status == 'p',
|
||||||
|
'bg-green-500': status == 'f',
|
||||||
|
'bg-red-500': status == 'a',
|
||||||
|
'bg-purple-500': status == 'r'
|
||||||
|
}"> </span>
|
||||||
|
<span x-text="status_display"></span>
|
||||||
|
</span>
|
||||||
|
<svg class="text-white w-3" viewBox="5 8 14 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 9L12 15L18 9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="absolute top-[105%] left-0 w-full whitespace-nowrap z-10 text-sm font-medium bg-gray-800/20 backdrop-blur-lg rounded-md rounded-t-none border border-gray-200 dark:border-gray-700"
|
||||||
|
x-show="open"
|
||||||
|
>
|
||||||
|
<ul class="[&_li:first-of-type]:rounded-none [&_li:last-of-type]:rounded-t-none">
|
||||||
|
{% for status_value, status_label in game_statuses %}
|
||||||
|
<li class="px-4 py-2 dark:hover:text-white dark:hover:bg-gray-700 dark:focus:ring-blue-500 dark:focus:text-white rounded">
|
||||||
|
<a href="#" @click.prevent.stop="setStatus('{{ status_value }}', '{{ status_label }}'); open = false;"
|
||||||
|
:class="{ 'font-bold': status === '{{ status_value }}' }">
|
||||||
|
<c-gamestatus display="flex" status="{{ status_value }}" class="text-slate-300">{{ status_label }}</c-gamestatus>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div x-show="saving" style="display: none;">Saving...</div>
|
||||||
|
{% if game.mastered %}👑{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 items-center"
|
<div class="flex gap-2 items-center"
|
||||||
x-data="{ open: false }"
|
x-data="{ open: false }"
|
||||||
|
|||||||
@@ -162,5 +162,4 @@ urlpatterns = [
|
|||||||
general.stats,
|
general.stats,
|
||||||
name="stats_by_year",
|
name="stats_by_year",
|
||||||
),
|
),
|
||||||
path("api/", api.urls),
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -375,6 +375,7 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
|
|||||||
"statuschange_count": statuschange_count,
|
"statuschange_count": statuschange_count,
|
||||||
"statuschanges": statuschanges,
|
"statuschanges": statuschanges,
|
||||||
"game": game,
|
"game": game,
|
||||||
|
"game_statuses": Game.Status.choices,
|
||||||
"playrange": playrange,
|
"playrange": playrange,
|
||||||
"purchase_count": game.purchases.count(),
|
"purchase_count": game.purchases.count(),
|
||||||
"session_average_without_manual": round(
|
"session_average_without_manual": round(
|
||||||
|
|||||||
@@ -22,8 +22,11 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from graphene_django.views import GraphQLView
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
|
from games.api import api
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", RedirectView.as_view(url="/tracker")),
|
path("", RedirectView.as_view(url="/tracker")),
|
||||||
|
path("api/", api.urls),
|
||||||
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
|
path("graphql", csrf_exempt(GraphQLView.as_view(graphiql=True))),
|
||||||
path("login/", auth_views.LoginView.as_view(), name="login"),
|
path("login/", auth_views.LoginView.as_view(), name="login"),
|
||||||
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
|
path("logout/", auth_views.LogoutView.as_view(), name="logout"),
|
||||||
|
|||||||
Reference in New Issue
Block a user