Compare commits

...

5 Commits

Author SHA1 Message Date
Lukáš Kucharczyk c15eaca205
only overflow table, not paginator, improve styling
Django CI/CD / test (push) Successful in 1m5s Details
Django CI/CD / build-and-push (push) Has been skipped Details
2024-08-09 12:42:54 +02:00
Lukáš Kucharczyk 496c99ccf1
formatting 2024-08-09 12:23:49 +02:00
Lukáš Kucharczyk 992622e8d1
make it possible to not use paginator when limit = 0 2024-08-09 12:23:40 +02:00
Lukáš Kucharczyk cabe36c822
add dark/light mode toggle 2024-08-09 12:22:26 +02:00
Lukáš Kucharczyk d84b67c460
improve pagination 2024-08-09 11:47:10 +02:00
8 changed files with 214 additions and 68 deletions

View File

@ -70,9 +70,15 @@ form label {
} }
@layer utilities { @layer utilities {
.min-w-20char {
min-width: 20ch;
}
.max-w-20char { .max-w-20char {
max-width: 20ch; max-width: 20ch;
} }
.min-w-30char {
min-width: 30ch;
}
.max-w-30char { .max-w-30char {
max-width: 30ch; max-width: 30ch;
} }

View File

@ -15,15 +15,24 @@ from games.views import dateformat
@login_required @login_required
def list_purchases(request: HttpRequest) -> HttpResponse: def list_purchases(request: HttpRequest) -> HttpResponse:
context: dict[Any, Any] = {} context: dict[Any, Any] = {}
paginator = Paginator(Purchase.objects.order_by("created_at"), 10)
page_number = request.GET.get("page", 1) page_number = request.GET.get("page", 1)
page_obj = paginator.get_page(page_number) limit = request.GET.get("limit", 10)
purchases = page_obj.object_list purchases = Purchase.objects.order_by("-date_purchased")
page_obj = None
if int(limit) != 0:
paginator = Paginator(Purchase.objects.order_by("-date_purchased"), limit)
page_obj = paginator.get_page(page_number)
purchases = page_obj.object_list
context = { context = {
"title": "Manage purchases", "title": "Manage purchases",
"page_obj": page_obj, "page_obj": page_obj or None,
"elided_page_range": page_obj.paginator.get_elided_page_range( "elided_page_range": (
page_number, on_each_side=1, on_ends=1 page_obj.paginator.get_elided_page_range(
page_number, on_each_side=1, on_ends=1
)
if page_obj
else None
), ),
"data": { "data": {
"columns": [ "columns": [

View File

@ -1511,6 +1511,10 @@ input:checked + .toggle-bg {
max-width: 20rem; max-width: 20rem;
} }
.max-w-screen-2xl {
max-width: 1536px;
}
.max-w-screen-lg { .max-w-screen-lg {
max-width: 1024px; max-width: 1024px;
} }
@ -1687,6 +1691,10 @@ input:checked + .toggle-bg {
white-space: nowrap; white-space: nowrap;
} }
.text-ellipsis {
text-overflow: ellipsis;
}
.whitespace-nowrap { .whitespace-nowrap {
white-space: nowrap; white-space: nowrap;
} }
@ -1821,6 +1829,16 @@ input:checked + .toggle-bg {
background-color: rgb(255 255 255 / 0.5); background-color: rgb(255 255 255 / 0.5);
} }
.bg-gray-300 {
--tw-bg-opacity: 1;
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
}
.bg-gray-400 {
--tw-bg-opacity: 1;
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
}
.p-1 { .p-1 {
padding: 0.25rem; padding: 0.25rem;
} }
@ -1883,10 +1901,6 @@ input:checked + .toggle-bg {
padding-bottom: 1rem; padding-bottom: 1rem;
} }
.pb-16 {
padding-bottom: 4rem;
}
.pl-3 { .pl-3 {
padding-left: 0.75rem; padding-left: 0.75rem;
} }
@ -1903,10 +1917,6 @@ input:checked + .toggle-bg {
padding-top: 0.5rem; padding-top: 0.5rem;
} }
.pt-4 {
padding-top: 1rem;
}
.pt-8 { .pt-8 {
padding-top: 2rem; padding-top: 2rem;
} }
@ -2067,6 +2077,16 @@ input:checked + .toggle-bg {
color: rgb(250 202 21 / var(--tw-text-opacity)); color: rgb(250 202 21 / var(--tw-text-opacity));
} }
.text-gray-300 {
--tw-text-opacity: 1;
color: rgb(209 213 219 / var(--tw-text-opacity));
}
.text-gray-100 {
--tw-text-opacity: 1;
color: rgb(243 244 246 / var(--tw-text-opacity));
}
.underline { .underline {
text-decoration-line: underline; text-decoration-line: underline;
} }
@ -2160,6 +2180,10 @@ input:checked + .toggle-bg {
max-width: 20ch; max-width: 20ch;
} }
.max-w-30char {
max-width: 30ch;
}
.\[a-zA-Z\:\\-\] { .\[a-zA-Z\:\\-\] {
a-z-a--z: \-; a-z-a--z: \-;
} }
@ -2732,6 +2756,11 @@ textarea:disabled:is(.dark *) {
color: rgb(107 114 128 / var(--tw-text-opacity)); color: rgb(107 114 128 / var(--tw-text-opacity));
} }
.dark\:text-gray-600:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.dark\:text-slate-400:is(.dark *) { .dark\:text-slate-400:is(.dark *) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity)); color: rgb(148 163 184 / var(--tw-text-opacity));
@ -2752,6 +2781,11 @@ textarea:disabled:is(.dark *) {
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
} }
.dark\:text-gray-200:is(.dark *) {
--tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity));
}
.odd\:dark\:bg-gray-900:is(.dark *):nth-child(odd) { .odd\:dark\:bg-gray-900:is(.dark *):nth-child(odd) {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(17 24 39 / var(--tw-bg-opacity)); background-color: rgb(17 24 39 / var(--tw-bg-opacity));
@ -2827,6 +2861,11 @@ textarea:disabled:is(.dark *) {
--tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity)); --tw-ring-color: rgb(63 131 248 / var(--tw-ring-opacity));
} }
.dark\:focus\:ring-gray-700:focus:is(.dark *) {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(55 65 81 / var(--tw-ring-opacity));
}
.dark\:focus\:ring-green-500:focus:is(.dark *) { .dark\:focus\:ring-green-500:focus:is(.dark *) {
--tw-ring-opacity: 1; --tw-ring-opacity: 1;
--tw-ring-color: rgb(14 159 110 / var(--tw-ring-opacity)); --tw-ring-color: rgb(14 159 110 / var(--tw-ring-opacity));
@ -2849,6 +2888,10 @@ textarea:disabled:is(.dark *) {
max-width: 36rem; max-width: 36rem;
} }
.sm\:max-w-screen-sm {
max-width: 640px;
}
.sm\:rounded-lg { .sm\:rounded-lg {
border-radius: 0.5rem; border-radius: 0.5rem;
} }
@ -2896,6 +2939,10 @@ textarea:disabled:is(.dark *) {
width: auto; width: auto;
} }
.md\:max-w-screen-md {
max-width: 768px;
}
.md\:flex-row { .md\:flex-row {
flex-direction: row; flex-direction: row;
} }
@ -2925,6 +2972,12 @@ textarea:disabled:is(.dark *) {
} }
} }
@media (min-width: 1536px) {
.\32xl\:max-w-screen-2xl {
max-width: 1536px;
}
}
.rtl\:rotate-180:where([dir="rtl"], [dir="rtl"] *) { .rtl\:rotate-180:where([dir="rtl"], [dir="rtl"] *) {
--tw-rotate: 180deg; --tw-rotate: 180deg;
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));

View File

@ -16,8 +16,16 @@
{% django_htmx_script %} {% django_htmx_script %}
<link rel="stylesheet" href="{% static 'base.css' %}" /> <link rel="stylesheet" href="{% static 'base.css' %}" />
<script src="https://cdn.jsdelivr.net/npm/flowbite@2.4.1/dist/flowbite.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/flowbite@2.4.1/dist/flowbite.min.js"></script>
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark')
}
</script>
</head> </head>
<body class="dark" hx-indicator="#indicator"> <body hx-indicator="#indicator">
<img id="indicator" <img id="indicator"
src="{% static 'icons/loading.png' %}" src="{% static 'icons/loading.png' %}"
class="absolute right-3 top-3 animate-spin htmx-indicator" class="absolute right-3 top-3 animate-spin htmx-indicator"
@ -39,6 +47,25 @@
</a> </a>
<div class="w-full md:block md:w-auto"> <div class="w-full md:block md:w-auto">
<ul class="flex flex-col md:flex-row p-4 mt-4 dark:text-white"> <ul class="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
<button id="theme-toggle"
type="button"
class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
<svg id="theme-toggle-dark-icon"
class="hidden w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
<svg id="theme-toggle-light-icon"
class="hidden w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" fill-rule="evenodd" clip-rule="evenodd">
</path>
</svg>
</button>
<li class="relative group"> <li class="relative group">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a class="block py-2 pl-3 pr-4 hover:underline" <a class="block py-2 pl-3 pr-4 hover:underline"
@ -97,7 +124,8 @@
</li> </li>
<li> <li>
<a class="block py-2 pl-3 pr-4 hover:underline" <a class="block py-2 pl-3 pr-4 hover:underline"
href="{% url 'list_sessions' %}">All Sessions</a> href="{% url 'list_sessions' %}">All
Sessions</a>
</li> </li>
<li> <li>
<a class="block py-2 pl-3 pr-4 hover:underline" <a class="block py-2 pl-3 pr-4 hover:underline"
@ -109,15 +137,58 @@
</div> </div>
</div> </div>
</nav> </nav>
<div class="flex flex-1 dark:bg-gray-800 justify-center pt-8 pb-16"> <div class="flex flex-1 flex-col dark:bg-gray-800 pt-8">
{% block content %} {% block content %}
No content here. No content here.
{% endblock content %} {% endblock content %}
</div> </div>
{% load version %} {% load version %}
<span class="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span> <span class="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date
%})</span>
</div> </div>
{% block scripts %} {% block scripts %}
{% endblock scripts %} {% endblock scripts %}
<script>
var themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
var themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
// Change the icons inside the button based on previous settings
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
themeToggleLightIcon.classList.remove('hidden');
} else {
themeToggleDarkIcon.classList.remove('hidden');
}
var themeToggleBtn = document.getElementById('theme-toggle');
themeToggleBtn.addEventListener('click', function () {
// toggle icons inside button
themeToggleDarkIcon.classList.toggle('hidden');
themeToggleLightIcon.classList.toggle('hidden');
// if set via local storage previously
if (localStorage.getItem('color-theme')) {
if (localStorage.getItem('color-theme') === 'light') {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
}
// if NOT set via local storage previously
} else {
if (document.documentElement.classList.contains('dark')) {
document.documentElement.classList.remove('dark');
localStorage.setItem('color-theme', 'light');
} else {
document.documentElement.classList.add('dark');
localStorage.setItem('color-theme', 'dark');
}
}
});
</script>
</body> </body>
</html> </html>

View File

@ -1,48 +1,51 @@
<div class="relative overflow-x-auto shadow-md sm:rounded-lg" <div class="shadow-md sm:rounded-lg" hx-boost="false">
hx-boost="false"> <div class="relative overflow-x-auto">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400"> <table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr> <tr>
{% for column in columns %}<th scope="col" class="px-6 py-3">{{ column }}</th>{% endfor %} {% for column in columns %}<th scope="col" class="px-6 py-3">{{ column }}</th>{% endfor %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for row in rows %} {% for row in rows %}
{% table_row data=row %} {% table_row data=row %}
{% endfor %}
</tbody>
</table>
<nav class="flex items-center flex-column flex-wrap md:flex-row justify-between pt-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.paginator.page_range.start }}-{{ page_obj.paginator.page_range.stop }}</span> of <span class="font-semibold text-gray-900 dark:text-white">{{ page_obj.paginator.count }}</span></span>
<ul class="inline-flex -space-x-px rtl:space-x-reverse text-sm h-8">
<li>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"
class="flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Previous</a>
{% else %}
<a aria-current="page"
class="cursor-not-allowed flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-s-lg dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">Previous</a>
{% endif %}
{% for page in elided_page_range %}
<li>
{% if page != page_obj.number %}
<a href="?page={{ page }}"
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{{ page }}</a>
{% else %}
<a aria-current="page"
class="cursor-not-allowed flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">{{ page }}</a>
{% endif %}
</li>
{% endfor %} {% endfor %}
{% if page_obj.has_next %} </tbody>
<a href="?page={{ page_obj.next_page_number }}" </table>
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Next</a> </div>
{% else %} {% if page_obj %}
<a aria-current="page" <nav class="flex items-center flex-column md:flex-row justify-between px-6 py-4"
class="cursor-not-allowed flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg dark:bg-gray-900 dark:border-gray-700 dark:text-gray-400">Next</a> aria-label="Table navigation">
{% endif %} <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>
</li> <ul class="inline-flex -space-x-px rtl:space-x-reverse text-sm h-8">
</ul> <li>
</nav> {% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"
class="flex items-center justify-center px-3 h-8 ms-0 leading-tight text-gray-500 bg-white border border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Previous</a>
{% else %}
<a aria-current="page"
class="cursor-not-allowed flex items-center justify-center px-3 h-8 leading-tight text-gray-300 bg-white border border-gray-300 rounded-s-lg dark:bg-gray-800 dark:border-gray-700 dark:text-gray-600">Previous</a>
{% endif %}
{% for page in elided_page_range %}
<li>
{% if page != page_obj.number %}
<a href="?page={{ page }}"
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">{{ page }}</a>
{% else %}
<a aria-current="page"
class="cursor-not-allowed flex items-center justify-center px-3 h-8 leading-tight text-white border bg-gray-400 border-gray-300 dark:bg-gray-900 dark:border-gray-700 dark:text-gray-200">{{ page }}</a>
{% endif %}
</li>
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}"
class="flex items-center justify-center px-3 h-8 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white">Next</a>
{% else %}
<a aria-current="page"
class="cursor-not-allowed flex items-center justify-center px-3 h-8 leading-tight text-gray-300 bg-white border border-gray-300 rounded-e-lg dark:bg-gray-800 dark:border-gray-700 dark:text-gray-600">Next</a>
{% endif %}
</li>
</ul>
</nav>
{% endif %}
</div> </div>

View File

@ -2,7 +2,9 @@
{% for td in data %} {% for td in data %}
{% if forloop.first %} {% if forloop.first %}
<th scope="row" <th scope="row"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{{ td }}</th> class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white min-w-30-char max-w-30char text-ellipsis overflow-hidden">
<span title="{{ td }}">{{ td }}</span>
</th>
{% else %} {% else %}
{% #table_td %} {% #table_td %}
{{ td }} {{ td }}
@ -10,6 +12,6 @@
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endfragment %} {% endfragment %}
<tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 border-b dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 border-b"> <tr class="odd:bg-white odd:dark:bg-gray-900 even:bg-gray-50 even:dark:bg-gray-800 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 border-b">
{{ children|default:default_content }} {{ children|default:default_content }}
</tr> </tr>

View File

@ -1 +1 @@
<td class="px-6 py-4">{{ children }}</td> <td class="px-6 py-4 min-w-20-char max-w-20-char">{{ children }}</td>

View File

@ -4,5 +4,7 @@
{{ title }} {{ title }}
{% endblock title %} {% endblock title %}
{% block content %} {% block content %}
{% simple_table columns=data.columns rows=data.rows page_obj=page_obj elided_page_range=elided_page_range %} <div class="2xl:max-w-screen-2xl md:max-w-screen-md sm:max-w-screen-sm self-center">
{% simple_table columns=data.columns rows=data.rows page_obj=page_obj elided_page_range=elided_page_range %}
</div>
{% endblock content %} {% endblock content %}