feat: implement frontend filter bars and integration across all list views
This commit is contained in:
@@ -501,6 +501,8 @@ class FilterPreset(models.Model):
|
||||
("sessions", "Sessions"),
|
||||
("purchases", "Purchases"),
|
||||
("playevents", "Play Events"),
|
||||
("devices", "Devices"),
|
||||
("platforms", "Platforms"),
|
||||
]
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
@@ -293,85 +293,27 @@
|
||||
--leading-5: 20px;
|
||||
--radius-base: 12px;
|
||||
--color-body: var(--color-gray-600);
|
||||
--color-body-subtle: var(--color-gray-500);
|
||||
--color-heading: var(--color-gray-900);
|
||||
--color-fg-brand-subtle: var(--color-blue-200);
|
||||
--color-fg-brand: var(--color-blue-700);
|
||||
--color-fg-brand-strong: var(--color-blue-900);
|
||||
--color-fg-success: var(--color-emerald-700);
|
||||
--color-fg-success-strong: var(--color-emerald-900);
|
||||
--color-fg-danger: var(--color-rose-700);
|
||||
--color-fg-danger-strong: var(--color-rose-900);
|
||||
--color-fg-warning-subtle: var(--color-orange-600);
|
||||
--color-fg-warning: var(--color-orange-900);
|
||||
--color-fg-yellow: var(--color-yellow-400);
|
||||
--color-fg-disabled: var(--color-gray-400);
|
||||
--color-fg-purple: var(--color-purple-600);
|
||||
--color-fg-cyan: var(--color-cyan-600);
|
||||
--color-fg-indigo: var(--color-indigo-600);
|
||||
--color-fg-pink: var(--color-pink-600);
|
||||
--color-fg-lime: var(--color-lime-600);
|
||||
--color-neutral-primary-soft: var(--color-white);
|
||||
--color-neutral-primary: var(--color-white);
|
||||
--color-neutral-primary-medium: var(--color-white);
|
||||
--color-neutral-primary-strong: var(--color-white);
|
||||
--color-neutral-secondary-soft: var(--color-gray-50);
|
||||
--color-neutral-secondary: var(--color-gray-50);
|
||||
--color-neutral-secondary-medium: var(--color-gray-50);
|
||||
--color-neutral-secondary-strong: var(--color-gray-50);
|
||||
--color-neutral-secondary-strongest: var(--color-gray-50);
|
||||
--color-neutral-tertiary-soft: var(--color-gray-100);
|
||||
--color-neutral-tertiary: var(--color-gray-100);
|
||||
--color-neutral-tertiary-medium: var(--color-gray-100);
|
||||
--color-neutral-quaternary: var(--color-gray-200);
|
||||
--color-neutral-quaternary-medium: var(--color-gray-200);
|
||||
--color-gray: var(--color-gray-300);
|
||||
--color-brand-softer: var(--color-blue-50);
|
||||
--color-brand-soft: var(--color-blue-100);
|
||||
--color-brand: var(--color-blue-700);
|
||||
--color-brand-medium: var(--color-blue-200);
|
||||
--color-brand-strong: var(--color-blue-800);
|
||||
--color-success-soft: var(--color-emerald-50);
|
||||
--color-success: var(--color-emerald-700);
|
||||
--color-success-medium: var(--color-emerald-100);
|
||||
--color-success-strong: var(--color-emerald-800);
|
||||
--color-danger-soft: var(--color-rose-50);
|
||||
--color-danger: var(--color-rose-700);
|
||||
--color-danger-medium: var(--color-rose-100);
|
||||
--color-danger-strong: var(--color-rose-800);
|
||||
--color-warning-soft: var(--color-orange-50);
|
||||
--color-warning: var(--color-orange-500);
|
||||
--color-warning-medium: var(--color-orange-100);
|
||||
--color-warning-strong: var(--color-orange-700);
|
||||
--color-dark-soft: var(--color-gray-800);
|
||||
--color-dark: var(--color-gray-800);
|
||||
--color-dark-strong: var(--color-gray-900);
|
||||
--color-disabled: var(--color-gray-100);
|
||||
--color-purple: var(--color-purple-500);
|
||||
--color-sky: var(--color-sky-500);
|
||||
--color-teal: var(--color-teal-600);
|
||||
--color-pink: var(--color-pink-600);
|
||||
--color-cyan: var(--color-cyan-500);
|
||||
--color-fuchsia: var(--color-fuchsia-600);
|
||||
--color-indigo: var(--color-indigo-600);
|
||||
--color-orange: var(--color-orange-400);
|
||||
--color-buffer: var(--color-white);
|
||||
--color-buffer-medium: var(--color-white);
|
||||
--color-buffer-strong: var(--color-white);
|
||||
--color-muted: var(--color-gray-50);
|
||||
--color-light-subtle: var(--color-gray-100);
|
||||
--color-light: var(--color-gray-100);
|
||||
--color-light-medium: var(--color-gray-100);
|
||||
--color-default-subtle: var(--color-gray-200);
|
||||
--color-default: var(--color-gray-200);
|
||||
--color-default-medium: var(--color-gray-200);
|
||||
--color-default-strong: var(--color-gray-200);
|
||||
--color-success-subtle: var(--color-emerald-200);
|
||||
--color-danger-subtle: var(--color-rose-200);
|
||||
--color-warning-subtle: var(--color-orange-200);
|
||||
--color-brand-subtle: var(--color-blue-200);
|
||||
--color-brand-light: var(--color-blue-600);
|
||||
--color-dark-subtle: var(--color-gray-800);
|
||||
--color-dark-backdrop: var(--color-gray-950);
|
||||
--color-accent: #7c3aed;
|
||||
}
|
||||
@@ -881,18 +823,12 @@
|
||||
.start-0 {
|
||||
inset-inline-start: calc(var(--spacing) * 0);
|
||||
}
|
||||
.end-1 {
|
||||
inset-inline-end: calc(var(--spacing) * 1);
|
||||
}
|
||||
.end-1\.5 {
|
||||
inset-inline-end: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
.top-0 {
|
||||
top: calc(var(--spacing) * 0);
|
||||
}
|
||||
.top-1 {
|
||||
top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.top-1\/2 {
|
||||
top: calc(1 / 2 * 100%);
|
||||
}
|
||||
@@ -914,9 +850,6 @@
|
||||
.bottom-0 {
|
||||
bottom: calc(var(--spacing) * 0);
|
||||
}
|
||||
.bottom-1 {
|
||||
bottom: calc(var(--spacing) * 1);
|
||||
}
|
||||
.bottom-1\.5 {
|
||||
bottom: calc(var(--spacing) * 1.5);
|
||||
}
|
||||
@@ -1626,15 +1559,9 @@
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.w-1 {
|
||||
width: calc(var(--spacing) * 1);
|
||||
}
|
||||
.w-1\/2 {
|
||||
width: calc(1 / 2 * 100%);
|
||||
}
|
||||
.w-2 {
|
||||
width: calc(var(--spacing) * 2);
|
||||
}
|
||||
.w-2\.5 {
|
||||
width: calc(var(--spacing) * 2.5);
|
||||
}
|
||||
@@ -1752,9 +1679,6 @@
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.-translate-x-full {
|
||||
--tw-translate-x: -100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -1771,10 +1695,6 @@
|
||||
--tw-translate-x: 100%;
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1 {
|
||||
--tw-translate-y: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1\/2 {
|
||||
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -2160,18 +2080,12 @@
|
||||
.bg-amber-50 {
|
||||
background-color: var(--color-amber-50);
|
||||
}
|
||||
.bg-amber-500 {
|
||||
background-color: var(--color-amber-500);
|
||||
}
|
||||
.bg-amber-500\/15 {
|
||||
background-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 15%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-amber-500) 15%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-black {
|
||||
background-color: var(--color-black);
|
||||
}
|
||||
.bg-black\/70 {
|
||||
background-color: color-mix(in srgb, #000 70%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2196,9 +2110,6 @@
|
||||
background-color: color-mix(in oklab, var(--color-brand) 15%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-dark-backdrop {
|
||||
background-color: var(--color-dark-backdrop);
|
||||
}
|
||||
.bg-dark-backdrop\/70 {
|
||||
background-color: color-mix(in srgb, oklch(13% 0.028 261.692) 70%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2217,18 +2128,12 @@
|
||||
.bg-gray-500 {
|
||||
background-color: var(--color-gray-500);
|
||||
}
|
||||
.bg-gray-800 {
|
||||
background-color: var(--color-gray-800);
|
||||
}
|
||||
.bg-gray-800\/20 {
|
||||
background-color: color-mix(in srgb, oklch(27.8% 0.033 256.848) 20%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
background-color: color-mix(in oklab, var(--color-gray-800) 20%, transparent);
|
||||
}
|
||||
}
|
||||
.bg-gray-900 {
|
||||
background-color: var(--color-gray-900);
|
||||
}
|
||||
.bg-gray-900\/50 {
|
||||
background-color: color-mix(in srgb, oklch(21% 0.034 264.665) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
@@ -2358,18 +2263,6 @@
|
||||
fill: white !important;
|
||||
}
|
||||
}
|
||||
.apexcharts-gridline {
|
||||
stroke: var(--color-default) !important;
|
||||
.dark & {
|
||||
stroke: var(--color-default) !important;
|
||||
}
|
||||
}
|
||||
.apexcharts-xcrosshairs {
|
||||
stroke: var(--color-default) !important;
|
||||
.dark & {
|
||||
stroke: var(--color-default) !important;
|
||||
}
|
||||
}
|
||||
.apexcharts-ycrosshairs {
|
||||
stroke: var(--color-default) !important;
|
||||
.dark & {
|
||||
@@ -2428,9 +2321,6 @@
|
||||
.px-6 {
|
||||
padding-inline: calc(var(--spacing) * 6);
|
||||
}
|
||||
.py-0 {
|
||||
padding-block: calc(var(--spacing) * 0);
|
||||
}
|
||||
.py-0\.5 {
|
||||
padding-block: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
@@ -2657,9 +2547,6 @@
|
||||
.text-balance {
|
||||
text-wrap: balance;
|
||||
}
|
||||
.text-wrap {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
@@ -2795,9 +2682,6 @@
|
||||
.line-through {
|
||||
text-decoration-line: line-through;
|
||||
}
|
||||
.no-underline {
|
||||
text-decoration-line: none;
|
||||
}
|
||||
.no-underline\! {
|
||||
text-decoration-line: none !important;
|
||||
}
|
||||
@@ -2864,10 +2748,6 @@
|
||||
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
}
|
||||
.backdrop-filter {
|
||||
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
||||
}
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
|
||||
+27
-4
@@ -2,6 +2,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from common.components import (
|
||||
A,
|
||||
@@ -10,19 +11,28 @@ from common.components import (
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
paginated_table_content,
|
||||
DeviceFilterBar,
|
||||
ModuleScript,
|
||||
)
|
||||
from common.layout import render_page
|
||||
from common.time import dateformat, local_strftime
|
||||
from common.utils import paginate
|
||||
from games.filters import parse_device_filter
|
||||
from games.forms import DeviceForm
|
||||
from games.models import Device
|
||||
|
||||
|
||||
@login_required
|
||||
def list_devices(request: HttpRequest) -> HttpResponse:
|
||||
devices, page_obj, elided_page_range = paginate(
|
||||
request, Device.objects.order_by("-created_at")
|
||||
)
|
||||
devices = Device.objects.order_by("-created_at")
|
||||
|
||||
filter_json = request.GET.get("filter", "")
|
||||
if filter_json:
|
||||
device_filter = parse_device_filter(filter_json)
|
||||
if device_filter is not None:
|
||||
devices = devices.filter(device_filter.to_q())
|
||||
|
||||
devices, page_obj, elided_page_range = paginate(request, devices)
|
||||
|
||||
data = {
|
||||
"header_action": A([], Button([], "Add device"), url_name="games:add_device"),
|
||||
@@ -61,7 +71,20 @@ def list_devices(request: HttpRequest) -> HttpResponse:
|
||||
elided_page_range=elided_page_range,
|
||||
request=request,
|
||||
)
|
||||
return render_page(request, content, title="Manage devices")
|
||||
filter_bar = DeviceFilterBar(
|
||||
filter_json=filter_json,
|
||||
preset_list_url=reverse("games:list_presets") + "?mode=devices",
|
||||
preset_save_url=reverse("games:save_preset") + "?mode=devices",
|
||||
)
|
||||
content = mark_safe(str(filter_bar) + str(content))
|
||||
return render_page(
|
||||
request,
|
||||
content,
|
||||
title="Manage devices",
|
||||
scripts=ModuleScript("range_slider.js")
|
||||
+ ModuleScript("search_select.js")
|
||||
+ ModuleScript("filter_bar.js"),
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
+27
-4
@@ -2,6 +2,7 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpRequest, HttpResponse
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from common.components import (
|
||||
A,
|
||||
@@ -10,10 +11,13 @@ from common.components import (
|
||||
ButtonGroup,
|
||||
Icon,
|
||||
paginated_table_content,
|
||||
PlatformFilterBar,
|
||||
ModuleScript,
|
||||
)
|
||||
from common.layout import render_page
|
||||
from common.time import dateformat, local_strftime
|
||||
from common.utils import paginate
|
||||
from games.filters import parse_platform_filter
|
||||
from games.forms import PlatformForm
|
||||
from games.models import Platform
|
||||
from games.views.general import use_custom_redirect
|
||||
@@ -21,9 +25,15 @@ from games.views.general import use_custom_redirect
|
||||
|
||||
@login_required
|
||||
def list_platforms(request: HttpRequest) -> HttpResponse:
|
||||
platforms, page_obj, elided_page_range = paginate(
|
||||
request, Platform.objects.order_by("name")
|
||||
)
|
||||
platforms = Platform.objects.order_by("name")
|
||||
|
||||
filter_json = request.GET.get("filter", "")
|
||||
if filter_json:
|
||||
platform_filter = parse_platform_filter(filter_json)
|
||||
if platform_filter is not None:
|
||||
platforms = platforms.filter(platform_filter.to_q())
|
||||
|
||||
platforms, page_obj, elided_page_range = paginate(request, platforms)
|
||||
|
||||
data = {
|
||||
"header_action": A(
|
||||
@@ -68,7 +78,20 @@ def list_platforms(request: HttpRequest) -> HttpResponse:
|
||||
elided_page_range=elided_page_range,
|
||||
request=request,
|
||||
)
|
||||
return render_page(request, content, title="Manage platforms")
|
||||
filter_bar = PlatformFilterBar(
|
||||
filter_json=filter_json,
|
||||
preset_list_url=reverse("games:list_presets") + "?mode=platforms",
|
||||
preset_save_url=reverse("games:save_preset") + "?mode=platforms",
|
||||
)
|
||||
content = mark_safe(str(filter_bar) + str(content))
|
||||
return render_page(
|
||||
request,
|
||||
content,
|
||||
title="Manage platforms",
|
||||
scripts=ModuleScript("range_slider.js")
|
||||
+ ModuleScript("search_select.js")
|
||||
+ ModuleScript("filter_bar.js"),
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
@@ -9,6 +9,8 @@ from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse
|
||||
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
from common.components import (
|
||||
A,
|
||||
AddForm,
|
||||
@@ -17,10 +19,12 @@ from common.components import (
|
||||
Icon,
|
||||
ModuleScript,
|
||||
paginated_table_content,
|
||||
PlayEventFilterBar,
|
||||
)
|
||||
from common.layout import render_page
|
||||
from common.time import dateformat, format_duration, local_strftime
|
||||
from common.utils import paginate
|
||||
from games.filters import parse_playevent_filter
|
||||
from games.forms import PlayEventForm
|
||||
from games.models import Game, PlayEvent, Session
|
||||
|
||||
@@ -126,9 +130,15 @@ def _get_formatted_playtime_for_game_sessions_in_range(
|
||||
|
||||
@login_required
|
||||
def list_playevents(request: HttpRequest) -> HttpResponse:
|
||||
playevents, page_obj, elided_page_range = paginate(
|
||||
request, PlayEvent.objects.order_by("-created_at")
|
||||
)
|
||||
playevents = PlayEvent.objects.order_by("-created_at")
|
||||
|
||||
filter_json = request.GET.get("filter", "")
|
||||
if filter_json:
|
||||
playevent_filter = parse_playevent_filter(filter_json)
|
||||
if playevent_filter is not None:
|
||||
playevents = playevents.filter(playevent_filter.to_q())
|
||||
|
||||
playevents, page_obj, elided_page_range = paginate(request, playevents)
|
||||
data = create_playevent_tabledata(playevents, request=request)
|
||||
content = paginated_table_content(
|
||||
data,
|
||||
@@ -136,7 +146,20 @@ def list_playevents(request: HttpRequest) -> HttpResponse:
|
||||
elided_page_range=elided_page_range,
|
||||
request=request,
|
||||
)
|
||||
return render_page(request, content, title="Manage play events")
|
||||
filter_bar = PlayEventFilterBar(
|
||||
filter_json=filter_json,
|
||||
preset_list_url=reverse("games:list_presets") + "?mode=playevents",
|
||||
preset_save_url=reverse("games:save_preset") + "?mode=playevents",
|
||||
)
|
||||
content = mark_safe(str(filter_bar) + str(content))
|
||||
return render_page(
|
||||
request,
|
||||
content,
|
||||
title="Manage play events",
|
||||
scripts=ModuleScript("range_slider.js")
|
||||
+ ModuleScript("search_select.js")
|
||||
+ ModuleScript("filter_bar.js"),
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
|
||||
Reference in New Issue
Block a user