mirror of
				https://github.com/ae-utbm/sith.git
				synced 2025-10-31 00:53:08 +00:00 
			
		
		
		
	improve pagination
This commit is contained in:
		| @@ -1,4 +1,3 @@ | ||||
|  | ||||
| {% extends "core/base.jinja" %} | ||||
|  | ||||
| {% block title %} | ||||
| @@ -9,6 +8,11 @@ | ||||
|   <script src="{{ static('core/js/alpinejs.min.js') }}" defer></script> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block additional_css %} | ||||
|   <link rel="stylesheet" href="{{ scss('pedagogy/css/pedagogy.scss') }}"> | ||||
|   <link rel="stylesheet" href="{{ scss('core/pagination.scss') }}"> | ||||
| {% endblock %} | ||||
|  | ||||
| {% block head %} | ||||
|   {{ super() }} | ||||
|   <meta name="viewport" content="width=device-width, initial-scale=0.6, maximum-scale=2"> | ||||
| @@ -40,101 +44,107 @@ | ||||
|         </div> | ||||
|         <div class="radio-department"> | ||||
|           <div class="radio-guide"> | ||||
|             {% for (display_name, real_name) in [ | ||||
|             {% set departments = [ | ||||
|             ("EDIM", "EDIM"), ("ENERGIE", "EE"), ("IMSI", "IMSI"), | ||||
|             ("INFO", "GI"), ("GMC", "MC"), ("HUMA", "HUMA"), ("TC", "TC") | ||||
|             ] %} | ||||
|             <input | ||||
|               type="checkbox" | ||||
|               name="department" | ||||
|               id="radio{{ real_name }}" | ||||
|               value="{{ real_name }}" | ||||
|               x-model="department" | ||||
|             /> | ||||
|             <label for="radio{{ real_name }}">{% trans %}{{ display_name }}{% endtrans %}</label> | ||||
| {% endfor %} | ||||
| </div> | ||||
| </div> | ||||
| <div class="radio-credit-type"> | ||||
|   <div class="radio-guide"> | ||||
|     {% for credit_type in ["CS", "TM", "EC", "QC", "OM"] %} | ||||
|       <input | ||||
|         type="checkbox" | ||||
|         name="credit_type" | ||||
|         id="radio{{ credit_type }}" | ||||
|         value="{{ credit_type }}" | ||||
|         x-model="credit_type" | ||||
|       /> | ||||
|       <label for="radio{{ credit_type }}">{% trans %}{{ credit_type }}{% endtrans %}</label> | ||||
|     {% endfor %} | ||||
|   </div> | ||||
| </div> | ||||
|             {% for (display_name, real_name) in departments %} | ||||
|               <input | ||||
|                 type="checkbox" | ||||
|                 name="department" | ||||
|                 id="radio{{ real_name }}" | ||||
|                 value="{{ real_name }}" | ||||
|                 x-model="department" | ||||
|               /> | ||||
|               <label for="radio{{ real_name }}">{% trans %}{{ display_name }}{% endtrans %}</label> | ||||
|             {% endfor %} | ||||
|           </div> | ||||
|         </div> | ||||
|         <div class="radio-credit-type"> | ||||
|           <div class="radio-guide"> | ||||
|             {% for credit_type in ["CS", "TM", "EC", "QC", "OM"] %} | ||||
|               <input | ||||
|                 type="checkbox" | ||||
|                 name="credit_type" | ||||
|                 id="radio{{ credit_type }}" | ||||
|                 value="{{ credit_type }}" | ||||
|                 x-model="credit_type" | ||||
|               /> | ||||
|               <label for="radio{{ credit_type }}">{% trans %}{{ credit_type }}{% endtrans %}</label> | ||||
|             {% endfor %} | ||||
|           </div> | ||||
|         </div> | ||||
|  | ||||
| <div class="radio-semester"> | ||||
|   <div class="radio-guide"> | ||||
|     <input type="checkbox" name="semester" id="radioAUTUMN" value="AUTUMN" x-model="semester"/> | ||||
|     <label for="radioAUTUMN"><i class="fa fa-leaf"></i></label> | ||||
|     <input type="checkbox" name="semester" id="radioSPRING" value="SPRING" x-model="semester"/> | ||||
|     <label for="radioSPRING"><i class="fa fa-sun-o"></i></label> | ||||
|         <div class="radio-semester"> | ||||
|           <div class="radio-guide"> | ||||
|             <input type="checkbox" name="semester" id="radioAUTUMN" value="AUTUMN" x-model="semester"/> | ||||
|             <label for="radioAUTUMN"><i class="fa fa-leaf"></i></label> | ||||
|             <input type="checkbox" name="semester" id="radioSPRING" value="SPRING" x-model="semester"/> | ||||
|             <label for="radioSPRING"><i class="fa fa-sun-o"></i></label> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     </form> | ||||
|     <table id="dynamic_view"> | ||||
|       <thead> | ||||
|         <tr> | ||||
|           <td>{% trans %}UV{% endtrans %}</td> | ||||
|           <td>{% trans %}Title{% endtrans %}</td> | ||||
|           <td>{% trans %}Department{% endtrans %}</td> | ||||
|           <td>{% trans %}Credit type{% endtrans %}</td> | ||||
|           <td><i class="fa fa-leaf"></i></td> | ||||
|           <td><i class="fa fa-sun-o"></i></td> | ||||
|           {% if can_create_uv %} | ||||
|             <td>{% trans %}Edit{% endtrans %}</td> | ||||
|             <td>{% trans %}Delete{% endtrans %}</td> | ||||
|           {% endif %} | ||||
|         </tr> | ||||
|       </thead> | ||||
|       <tbody id="dynamic_view_content"> | ||||
|         <template x-for="uv in uvs.results" :key="uv.id"> | ||||
|           <tr @click="window.location.href = `/pedagogy/uv/${uv.id}`" class="clickable"> | ||||
|             <td><a :href="`/pedagogy/uv/${uv.id}`" x-text="uv.code"></a></td> | ||||
|             <td x-text="uv.title"></td> | ||||
|             <td x-text="uv.department"></td> | ||||
|             <td x-text="uv.credit_type"></td> | ||||
|             <td><i :class="uv.semester.includes('AUTUMN') && 'fa fa-leaf'"></i></td> | ||||
|             <td><i :class="uv.semester.includes('SPRING') && 'fa fa-sun-o'"></i></td> | ||||
|             {% if can_create_uv -%} | ||||
|               <td><a :href="`/pedagogy/uv/${uv.id}/edit`">{% trans %}Edit{% endtrans %}</a></td> | ||||
|               <td><a :href="`/pedagogy/uv/${uv.id}/delete`">{% trans %}Delete{% endtrans %}</a></td> | ||||
|             {%- endif -%} | ||||
|           </tr> | ||||
|         </template> | ||||
|       </tbody> | ||||
|     </table> | ||||
|     <nav class="pagination" x-show="max_page() > 1"> | ||||
|       <button @click="page--" :disabled="page <= 1"> | ||||
|         <i class="fa fa-caret-left"></i> | ||||
|       </button> | ||||
|       <template x-for="i in max_page()"> | ||||
|         <button x-text="i" @click="page = i" :class="(page === i) && 'active'"></button> | ||||
|       </template> | ||||
|       <button @click="page++" :disabled="page >= max_page()"> | ||||
|         <i class="fa fa-caret-right"></i> | ||||
|       </button> | ||||
|     </nav> | ||||
|   </div> | ||||
| </div> | ||||
| </div> | ||||
| </form> | ||||
| <table id="dynamic_view"> | ||||
|   <thead> | ||||
|     <tr> | ||||
|       <td>{% trans %}UV{% endtrans %}</td> | ||||
|       <td>{% trans %}Title{% endtrans %}</td> | ||||
|       <td>{% trans %}Department{% endtrans %}</td> | ||||
|       <td>{% trans %}Credit type{% endtrans %}</td> | ||||
|       <td><i class="fa fa-leaf"></i></td> | ||||
|       <td><i class="fa fa-sun-o"></i></td> | ||||
|       {% if can_create_uv %} | ||||
|         <td>{% trans %}Edit{% endtrans %}</td> | ||||
|         <td>{% trans %}Delete{% endtrans %}</td> | ||||
|       {% endif %} | ||||
|     </tr> | ||||
|   </thead> | ||||
|   <tbody id="dynamic_view_content"> | ||||
|     <template x-for="uv in uvs.results" :key="uv.id"> | ||||
|       <tr @click="window.location.href = `/pedagogy/uv/${uv.id}`"> | ||||
|         <td><a :href="`/pedagogy/uv/${uv.id}`" x-text="uv.code"></a></td> | ||||
|         <td x-text="uv.title"></td> | ||||
|         <td x-text="uv.department"></td> | ||||
|         <td x-text="uv.credit_type"></td> | ||||
|         <td><i :class="uv.semester.includes('AUTUMN') && 'fa fa-leaf'"></i></td> | ||||
|         <td><i :class="uv.semester.includes('SPRING') && 'fa fa-sun-o'"></i></td> | ||||
|         {% if can_create_uv -%} | ||||
|           <td><a :href="`/pedagogy/uv/${uv.id}/edit`">{% trans %}Edit{% endtrans %}</a></td> | ||||
|           <td><a :href="`/pedagogy/uv/${uv.id}/delete`">{% trans %}Delete{% endtrans %}</a></td> | ||||
|         {%- endif -%} | ||||
|       </tr> | ||||
|     </template> | ||||
|   </tbody> | ||||
| </table> | ||||
| <nav id="pagination" class="hidden" :style="max_page() == 0 && 'display: none;'"> | ||||
|   <button @click="page--" :disabled="page == 1">{% trans %}Previous{% endtrans %}</button> | ||||
|   <template x-for="i in max_page()"> | ||||
|     <button x-text="i" @click="page = i" :class="i == page && 'active'"></button> | ||||
|   </template> | ||||
|   <button @click="page++" :disabled="page == max_page()">{% trans %}Next{% endtrans %}</button> | ||||
| </nav> | ||||
| </div> | ||||
| <script> | ||||
|   const initialUrlParams = new URLSearchParams(window.location.search); | ||||
|   <script> | ||||
|     const initialUrlParams = new URLSearchParams(window.location.search); | ||||
|  | ||||
|   function update_query_string(key, value) { | ||||
|     const url = new URL(window.location.href); | ||||
|     if (!value) { | ||||
|       url.searchParams.delete(key) | ||||
|     } else if (Array.isArray(value)) { | ||||
|       url.searchParams.delete(key) | ||||
|       value.forEach((v) => url.searchParams.append(key, v)) | ||||
|     } else { | ||||
|       url.searchParams.set(key, value); | ||||
|     function update_query_string(key, value) { | ||||
|       const url = new URL(window.location.href); | ||||
|       if (!value) { | ||||
|             {# If the value is null, undefined or empty => delete it #} | ||||
|         url.searchParams.delete(key) | ||||
|       } else if (Array.isArray(value)) { | ||||
|         url.searchParams.delete(key) | ||||
|         value.forEach((v) => url.searchParams.append(key, v)) | ||||
|       } else { | ||||
|         url.searchParams.set(key, value); | ||||
|       } | ||||
|       history.pushState(null, document.title, url.toString()); | ||||
|     } | ||||
|     history.pushState(null, document.title, url.toString()); | ||||
|   } | ||||
|  | ||||
|     {# | ||||
|     How does this work : | ||||
| @@ -145,50 +155,50 @@ | ||||
|     then fetch the corresponding data from the API. | ||||
|     This data will then be displayed on the result part of the page. | ||||
|     #} | ||||
|   const page_default = 1; | ||||
|   const page_size_default = 100; | ||||
|   document.addEventListener("alpine:init", () => { | ||||
|     Alpine.data("uv_search", () => ({ | ||||
|       uvs: [], | ||||
|       page: initialUrlParams.get("page") || page_default, | ||||
|       page_size: initialUrlParams.get("page_size") || page_size_default, | ||||
|       search: initialUrlParams.get("search") || "", | ||||
|       department: initialUrlParams.getAll("department"), | ||||
|       credit_type: initialUrlParams.getAll("credit_type"), | ||||
|     const page_default = 1; | ||||
|     const page_size_default = 100; | ||||
|     document.addEventListener("alpine:init", () => { | ||||
|       Alpine.data("uv_search", () => ({ | ||||
|         uvs: [], | ||||
|         page: parseInt(initialUrlParams.get("page")) || page_default, | ||||
|         page_size: parseInt(initialUrlParams.get("page_size")) || page_size_default, | ||||
|         search: initialUrlParams.get("search") || "", | ||||
|         department: initialUrlParams.getAll("department"), | ||||
|         credit_type: initialUrlParams.getAll("credit_type"), | ||||
|             {# The semester is easier to use on the backend as an enum (spring/autumn/both/none) | ||||
|             and easier to use on the frontend as an array ([spring, autumn]). | ||||
|             Thus there is some conversion involved when both communicate together #} | ||||
|       semester: initialUrlParams.has("semester") ? | ||||
|       initialUrlParams.get("semester").split("_AND_") : [], | ||||
|         semester: initialUrlParams.has("semester") ? | ||||
|         initialUrlParams.get("semester").split("_AND_") : [], | ||||
|  | ||||
|       async init() { | ||||
|         let search_params = ["search", "department", "credit_type", "semester"]; | ||||
|         let pagination_params = ["semester", "page"]; | ||||
|         search_params.forEach((param) => { | ||||
|           this.$watch(param, async () => { | ||||
|         async init() { | ||||
|           let search_params = ["search", "department", "credit_type", "semester"]; | ||||
|           let pagination_params = ["semester", "page"]; | ||||
|           search_params.forEach((param) => { | ||||
|             this.$watch(param, async () => { | ||||
|                         {# Reset pagination on search #} | ||||
|             this.page = page_default; | ||||
|             this.page_size = page_size_default; | ||||
|               this.page = page_default; | ||||
|               this.page_size = page_size_default; | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
|         search_params.concat(pagination_params).forEach((param) => { | ||||
|           this.$watch(param, async (value) => { | ||||
|             update_query_string(param, value); | ||||
|             await this.fetch_data();  {# reload data on form change #} | ||||
|           search_params.concat(pagination_params).forEach((param) => { | ||||
|             this.$watch(param, async (value) => { | ||||
|               update_query_string(param, value); | ||||
|               await this.fetch_data();  {# reload data on form change #} | ||||
|             }); | ||||
|           }); | ||||
|         }); | ||||
|         await this.fetch_data();  {# load initial data #} | ||||
|       }, | ||||
|           await this.fetch_data();  {# load initial data #} | ||||
|         }, | ||||
|  | ||||
|       async fetch_data() { | ||||
|         const url = "{{ url("api:fetch_uvs") }}" + window.location.search; | ||||
|         this.uvs = await (await fetch(url)).json(); | ||||
|       }, | ||||
|         async fetch_data() { | ||||
|           const url = "{{ url("api:fetch_uvs") }}" + window.location.search; | ||||
|           this.uvs = await (await fetch(url)).json(); | ||||
|         }, | ||||
|  | ||||
|       max_page() { | ||||
|         return Math.round(this.uvs.count / this.page_size); | ||||
|       } | ||||
|     })) | ||||
|   }) | ||||
| </script> | ||||
|         max_page() { | ||||
|           return Math.ceil(this.uvs.count / this.page_size); | ||||
|         } | ||||
|       })) | ||||
|     }) | ||||
|   </script> | ||||
| {% endblock content %} | ||||
		Reference in New Issue
	
	Block a user