django-spire 0.21.1__py3-none-any.whl → 0.22.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. django_spire/consts.py +1 -1
  2. django_spire/contrib/generic_views/portal_views.py +43 -0
  3. django_spire/core/models.py +9 -0
  4. django_spire/core/static/django_spire/css/bootstrap-extension.css +41 -0
  5. django_spire/core/static/django_spire/css/bootstrap-override.css +28 -4
  6. django_spire/core/{tags → tag}/intelligence/tag_set_bot.py +5 -4
  7. django_spire/core/tag/mixins.py +23 -0
  8. django_spire/core/{tags → tag}/models.py +1 -2
  9. django_spire/core/tag/service/tag_service.py +72 -0
  10. django_spire/core/{tags → tag}/tests/test_intelligence.py +1 -1
  11. django_spire/core/tag/tests/test_tags.py +102 -0
  12. django_spire/core/tag/tools.py +66 -0
  13. django_spire/core/templates/django_spire/dropdown/ellipsis_table_dropdown.html +26 -0
  14. django_spire/core/templates/django_spire/filtering/form/base_session_filter_form.html +4 -4
  15. django_spire/core/templates/django_spire/modal/content/dispatch_modal_delete_confirmation_content.html +6 -4
  16. django_spire/core/templates/django_spire/table/base.html +409 -0
  17. django_spire/core/templates/django_spire/table/element/child_row.html +59 -0
  18. django_spire/core/templates/django_spire/table/element/expandable_row.html +45 -0
  19. django_spire/core/templates/django_spire/table/element/footer.html +17 -0
  20. django_spire/core/templates/django_spire/table/element/header.html +9 -0
  21. django_spire/core/templates/django_spire/table/element/loading_skeleton.html +13 -0
  22. django_spire/core/templates/django_spire/table/element/refreshing_skeleton.html +13 -0
  23. django_spire/core/templates/django_spire/table/element/row.html +94 -0
  24. django_spire/core/templates/django_spire/table/element/trigger.html +1 -0
  25. django_spire/core/templates/django_spire/table/item/no_data_item.html +11 -0
  26. django_spire/core/templates/django_spire/tag/element/tag.html +1 -0
  27. django_spire/core/templatetags/spire_core_tags.py +12 -0
  28. django_spire/knowledge/collection/models.py +2 -2
  29. django_spire/knowledge/collection/services/tag_service.py +16 -14
  30. django_spire/knowledge/entry/models.py +2 -2
  31. django_spire/knowledge/entry/querysets.py +5 -0
  32. django_spire/knowledge/entry/services/tag_service.py +5 -5
  33. django_spire/knowledge/intelligence/bots/entries_search_llm_bot.py +44 -0
  34. django_spire/knowledge/intelligence/intel/entry_intel.py +24 -4
  35. django_spire/knowledge/intelligence/workflows/knowledge_workflow.py +46 -25
  36. django_spire/knowledge/models.py +12 -4
  37. django_spire/knowledge/templates/django_spire/knowledge/collection/page/display_page.html +6 -4
  38. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/editor_page.html +3 -2
  39. django_spire/knowledge/templates/django_spire/knowledge/message/knowledge_message_intel.html +14 -6
  40. django_spire/settings.py +1 -1
  41. {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/METADATA +2 -2
  42. {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/RECORD +50 -40
  43. django_spire/core/tags/mixins.py +0 -61
  44. django_spire/core/tags/tests/test_tags.py +0 -102
  45. django_spire/core/tags/tools.py +0 -20
  46. django_spire/knowledge/intelligence/bots/entry_search_llm_bot.py +0 -45
  47. django_spire/knowledge/intelligence/decoders/collection_decoder.py +0 -19
  48. django_spire/knowledge/intelligence/decoders/entry_decoder.py +0 -22
  49. django_spire/knowledge/intelligence/intel/collection_intel.py +0 -8
  50. django_spire/knowledge/templates/django_spire/knowledge/entry/version/page/form_page.html +0 -26
  51. /django_spire/core/{tags → tag}/__init__.py +0 -0
  52. /django_spire/core/{tags → tag}/intelligence/__init__.py +0 -0
  53. /django_spire/core/{tags → tag}/querysets.py +0 -0
  54. /django_spire/core/{tags/tests → tag/service}/__init__.py +0 -0
  55. /django_spire/{knowledge/intelligence/decoders → core/tag/tests}/__init__.py +0 -0
  56. {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/WHEEL +0 -0
  57. {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/licenses/LICENSE.md +0 -0
  58. {django_spire-0.21.1.dist-info → django_spire-0.22.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,409 @@
1
+ <div
2
+ x-data="{
3
+ current_page: parseInt('{{ current_page|default:0 }}'),
4
+ endpoint: '{{ endpoint }}',
5
+ has_next: {{ has_next|default:'true'|yesno:'true,false' }},
6
+ batch_size: parseInt('{{ batch_size|default:25 }}'),
7
+ responsive_mode: '{{ responsive_mode|default:"collapse" }}',
8
+ shared_payload: {},
9
+ skeleton_row: parseInt('{{ batch_size|default:25 }}'),
10
+ sort_column: {% if sort_column %}'{{ sort_column }}'{% else %}null{% endif %},
11
+ sort_direction: '{{ sort_direction|default:'asc' }}',
12
+ table_id: $id('table'),
13
+ total_count: parseInt('{{ total_count|default:0 }}'),
14
+
15
+ any_row_has_children: false,
16
+ average_row_height: 0,
17
+ column_breakpoints: [],
18
+ column_count: 0,
19
+ is_loading: false,
20
+ is_refreshing: false,
21
+ is_row_open: {},
22
+ loaded_count: 0,
23
+ observer: null,
24
+ select_all: false,
25
+ selected_rows: new Set(),
26
+ skeleton_count: 0,
27
+ prevent_auto_load: false,
28
+
29
+ {% block table_xdata %}{% endblock %}
30
+
31
+ async init() {
32
+ await this.$nextTick();
33
+ this.reset_scroll_position();
34
+
35
+ if (this.should_load_initial_data()) {
36
+ this.skeleton_count = this.skeleton_row;
37
+ await this.load_more();
38
+ }
39
+
40
+ if (this.has_next) {
41
+ setTimeout(() => this.setup_observer(), 500);
42
+ }
43
+ },
44
+
45
+ apply_column_breakpoints(row_element) {
46
+ if (this.responsive_mode !== 'collapse') return;
47
+
48
+ let cells = Array.from(row_element.children);
49
+
50
+ cells.forEach((cell, index) => {
51
+ let breakpoint = this.column_breakpoints[index];
52
+
53
+ if (breakpoint && cell.tagName === 'TD') {
54
+ cell.classList.add('d-none', `d-${breakpoint}-table-cell`);
55
+ }
56
+ });
57
+ },
58
+
59
+ build_request_params(next_page) {
60
+ let params = new URLSearchParams({
61
+ page: next_page,
62
+ batch_size: this.batch_size
63
+ });
64
+
65
+ if (this.sort_column) {
66
+ params.set('sort', this.sort_column);
67
+ params.set('direction', this.sort_direction);
68
+ }
69
+
70
+ return params;
71
+ },
72
+
73
+ async check_container_height() {
74
+ let table_container = this.$refs.table_container;
75
+ let trigger = this.$refs.infinite_scroll_trigger;
76
+
77
+ if (!table_container || !trigger || !this.has_next || this.is_refreshing || this.prevent_auto_load || this.is_loading) {
78
+ return;
79
+ }
80
+
81
+ await new Promise(resolve => setTimeout(resolve, 50));
82
+
83
+ let container_rect = table_container.getBoundingClientRect();
84
+ let trigger_rect = trigger.getBoundingClientRect();
85
+
86
+ if (trigger_rect.top <= container_rect.bottom) {
87
+ await this.load_more();
88
+ }
89
+ },
90
+
91
+ cleanup_loading_state() {
92
+ this.is_loading = false;
93
+ this.skeleton_count = 0;
94
+ },
95
+
96
+ disconnect_observer() {
97
+ if (this.observer) {
98
+ this.observer.disconnect();
99
+ }
100
+ },
101
+
102
+ async fetch_rows(next_page) {
103
+ if (!this.endpoint) {
104
+ return { success: false, error: 'No endpoint provided' };
105
+ }
106
+
107
+ let params = this.build_request_params(next_page);
108
+ let url = `${this.endpoint}?${params}`;
109
+ let view = new ViewGlue(url, this.$refs.shared_payload ? JSON.parse(this.$refs.shared_payload.textContent) : {});
110
+
111
+ if (next_page === 1) {
112
+ this.$dispatch('clear-rows', { table_id: this.table_id });
113
+ this.$refs.table_body.innerHTML = '';
114
+ }
115
+
116
+ let previous_count = this.loaded_count;
117
+
118
+ await view.render_insert_adjacent(this.$refs.table_body, {}, 'beforeend');
119
+
120
+ let added = this.loaded_count - previous_count;
121
+
122
+ return { success: true, added: added };
123
+ },
124
+
125
+ get_sort_icon(column) {
126
+ if (this.sort_column !== column) {
127
+ return 'bi-chevron-expand';
128
+ }
129
+ return this.sort_direction === 'asc' ? 'bi-chevron-up' : 'bi-chevron-down';
130
+ },
131
+
132
+ handle_header_mounted(event) {
133
+ this.column_count = event.detail.count;
134
+ },
135
+
136
+ handle_header_registered(event) {
137
+ this.column_breakpoints[event.detail.index] = event.detail.breakpoint;
138
+ },
139
+
140
+ handle_row_deselected(event) {
141
+ this.selected_rows.delete(event.detail.row_id);
142
+ },
143
+
144
+ handle_row_mounted(event) {
145
+ this.loaded_count++;
146
+
147
+ if (event.detail.row_element) {
148
+ this.apply_column_breakpoints(event.detail.row_element);
149
+
150
+ if (this.average_row_height === 0) {
151
+ this.average_row_height = event.detail.row_element.offsetHeight;
152
+ }
153
+ }
154
+ },
155
+
156
+ handle_row_selected(event) {
157
+ this.selected_rows.add(event.detail.row_id);
158
+ },
159
+
160
+ handle_rows_cleared() {
161
+ this.loaded_count = 0;
162
+ this.selected_rows.clear();
163
+ this.select_all = false;
164
+ },
165
+
166
+ handle_toggle_all() {
167
+ this.select_all = !this.select_all;
168
+
169
+ if (this.select_all) {
170
+ this.$dispatch('select-all-rows', { table_id: this.table_id });
171
+ } else {
172
+ this.selected_rows.clear();
173
+ this.$dispatch('deselect-all-rows', { table_id: this.table_id });
174
+ }
175
+ },
176
+
177
+ handle_toggle_row(event) {
178
+ let row_id = event.detail.row_id;
179
+ this.is_row_open[row_id] = !this.is_row_open[row_id];
180
+
181
+ this.$dispatch('toggle-row-state', { row_id: row_id, is_open: this.is_row_open[row_id], table_id: this.table_id });
182
+ },
183
+
184
+ handle_total_count_updated(event) {
185
+ this.total_count = event.detail.total_count;
186
+
187
+ if (event.detail.batch_size) {
188
+ this.batch_size = event.detail.batch_size;
189
+ }
190
+ },
191
+
192
+ async load_more() {
193
+ if (!this.endpoint) {
194
+ return;
195
+ }
196
+
197
+ this.is_loading = true;
198
+ this.skeleton_count = this.batch_size;
199
+
200
+ if (!this.$refs.table_body) {
201
+ this.cleanup_loading_state();
202
+ return;
203
+ }
204
+
205
+ let result = await this.fetch_rows(this.current_page + 1);
206
+
207
+ if (!result.success) {
208
+ this.cleanup_loading_state();
209
+ return;
210
+ }
211
+
212
+ if (result.added > 0) {
213
+ this.current_page++;
214
+ }
215
+
216
+ if (result.added < this.batch_size) {
217
+ this.has_next = false;
218
+ this.disconnect_observer();
219
+ }
220
+
221
+ this.cleanup_loading_state();
222
+
223
+ await this.$nextTick();
224
+ await this.check_container_height();
225
+ },
226
+
227
+ async refresh_table() {
228
+ if (this.is_refreshing || this.is_loading) {
229
+ return;
230
+ }
231
+
232
+ this.disconnect_observer();
233
+
234
+ this.prevent_auto_load = true;
235
+ this.skeleton_count = this.batch_size;
236
+ this.is_refreshing = true;
237
+ this.select_all = false;
238
+
239
+ await this.$nextTick();
240
+
241
+ this.reset_table_state();
242
+
243
+ let result = await this.fetch_rows(1);
244
+ this.update_counts_after_refresh(result);
245
+
246
+ await this.$nextTick();
247
+ await new Promise(resolve => setTimeout(resolve, 150));
248
+
249
+ this.is_refreshing = false;
250
+ this.skeleton_count = 0;
251
+
252
+ if (this.has_next) {
253
+ setTimeout(() => {
254
+ this.setup_observer();
255
+
256
+ setTimeout(() => {
257
+ this.prevent_auto_load = false;
258
+ }, 100);
259
+ }, 500);
260
+ }
261
+ },
262
+
263
+ reset_scroll_position() {
264
+ requestAnimationFrame(() => {
265
+ if (this.$refs.table_container) {
266
+ this.$refs.table_container.scrollTop = 0;
267
+ }
268
+ });
269
+ },
270
+
271
+ reset_table_state() {
272
+ this.current_page = 1;
273
+ this.has_next = true;
274
+ this.is_row_open = {};
275
+ },
276
+
277
+ async setup_observer() {
278
+ let trigger = this.$refs.infinite_scroll_trigger;
279
+
280
+ if (!trigger) {
281
+ return;
282
+ }
283
+
284
+ let options = {
285
+ root: null,
286
+ rootMargin: '200px',
287
+ threshold: 0.01
288
+ };
289
+
290
+ this.observer = new IntersectionObserver(
291
+ (entries) => {
292
+ entries.forEach(async entry => {
293
+ if (entry.isIntersecting && this.has_next && !this.is_loading && !this.prevent_auto_load) {
294
+ await this.load_more();
295
+ }
296
+ });
297
+ },
298
+ options
299
+ );
300
+
301
+ this.observer.observe(trigger);
302
+ },
303
+
304
+ should_load_initial_data() {
305
+ return this.loaded_count === 0 && this.current_page === 0;
306
+ },
307
+
308
+ async sort_by(column) {
309
+ if (this.is_refreshing || this.is_loading) {
310
+ return;
311
+ }
312
+
313
+ if (this.sort_column === column) {
314
+ this.sort_direction = this.sort_direction === 'asc' ? 'desc' : 'asc';
315
+ } else {
316
+ this.sort_column = column;
317
+ this.sort_direction = 'asc';
318
+ }
319
+
320
+ await this.refresh_table();
321
+ },
322
+
323
+ update_counts_after_refresh(result) {
324
+ if (result && result.added < this.batch_size) {
325
+ this.has_next = false;
326
+ }
327
+ },
328
+ }"
329
+ @clear-rows.window="if (!$event.detail || $event.detail.table_id === table_id) handle_rows_cleared()"
330
+ @filter-applied.window="if (!$event.detail || $event.detail.table_id === table_id) refresh_table()"
331
+ @header-mounted="handle_header_mounted($event)"
332
+ @header-registered="handle_header_registered($event)"
333
+ @row-deselected.window="if (!$event.detail || $event.detail.table_id === table_id) handle_row_deselected($event)"
334
+ @row-mounted.window="if (!$event.detail || $event.detail.table_id === table_id) handle_row_mounted($event)"
335
+ @row-selected.window="if (!$event.detail || $event.detail.table_id === table_id) handle_row_selected($event)"
336
+ @toggle-row.window="if (!$event.detail || $event.detail.table_id === table_id) handle_toggle_row($event)"
337
+ @total-count-updated.window="if (!$event.detail || $event.detail.table_id === table_id) handle_total_count_updated($event)"
338
+ >
339
+ <script type="application/json" x-ref="shared_payload">{% if shared_payload %}{{ shared_payload|safe }}{% else %}{}{% endif %}</script>
340
+
341
+ {% block table_toolbar %}{% endblock %}
342
+
343
+ {% block table_container %}
344
+ <div
345
+ class="position-relative table-container"
346
+ style="height: {{ table_height|default:'600px' }}; overflow-y: auto; overflow-x: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch;"
347
+ x-ref="table_container"
348
+ :data-table-id="table_id"
349
+ >
350
+ <table
351
+ class="table table-striped table-hover fs-7 w-100 {% block table_class %}{% endblock %}"
352
+ :style="'min-width: ' + (responsive_mode === 'scroll' ? '100%' : '800px') + '; ' + (any_row_has_children ? 'table-layout: fixed;' : 'table-layout: auto;')"
353
+ >
354
+ <thead class="z-1 {% block table_head_class %}bg-app-layer-two sticky-top{% endblock %}">
355
+ <tr
356
+ x-init="column_count = $el.children.length; $dispatch('header-mounted', { count: column_count })"
357
+ x-ref="header_row"
358
+ >
359
+ {% block table_header_container %}
360
+ {% block table_header_start %}
361
+ <th style="min-width: 30px; width: 30px;">
362
+ <input
363
+ :checked="select_all"
364
+ :disabled="is_refreshing || is_loading"
365
+ @change="handle_toggle_all()"
366
+ type="checkbox"
367
+ >
368
+ </th>
369
+
370
+ <th style="min-width: 30px; width: 30px;"></th>
371
+ {% endblock %}
372
+
373
+ {% block table_header %}{% endblock %}
374
+
375
+ {% block table_header_end %}
376
+ <th style="min-width: 70px; width: 70px;">Actions</th>
377
+ {% endblock %}
378
+ {% endblock %}
379
+ </tr>
380
+ </thead>
381
+
382
+ <tbody
383
+ x-ref="table_body"
384
+ x-show="!is_refreshing"
385
+ :data-table-id="table_id"
386
+ >
387
+ {% block refreshing_skeleton %}
388
+ {% include 'django_spire/table/element/refreshing_skeleton.html' %}
389
+ {% endblock %}
390
+
391
+ {% block table_body %}
392
+ {% endblock %}
393
+
394
+ {% block loading_skeleton %}
395
+ {% include 'django_spire/table/element/loading_skeleton.html' %}
396
+ {% endblock %}
397
+ </tbody>
398
+ </table>
399
+
400
+ {% block trigger %}
401
+ {% include 'django_spire/table/element/trigger.html' %}
402
+ {% endblock %}
403
+ </div>
404
+ {% endblock %}
405
+
406
+ {% block table_footer %}
407
+ {% include 'django_spire/table/element/footer.html' %}
408
+ {% endblock %}
409
+ </div>
@@ -0,0 +1,59 @@
1
+ <div
2
+ class="{{ wrapper_class|default:'' }}"
3
+ x-data="{
4
+ is_loading: false,
5
+ is_open: false,
6
+ has_loaded: false,
7
+ row_id: null,
8
+ endpoint: '{{ child_endpoint }}',
9
+ table_id: null,
10
+
11
+ init() {
12
+ this.table_id = this.$el.closest('[data-table-id]').dataset.tableId;
13
+
14
+ let parent = this.$el.closest('[data-row-id]');
15
+ this.row_id = parent ? parent.dataset.rowId : null;
16
+
17
+ this.$nextTick(() => {
18
+ if (this.row_id) {
19
+ this.$dispatch('child-registered', { row_id: this.row_id, table_id: this.table_id });
20
+ }
21
+ });
22
+ },
23
+
24
+ async load_children() {
25
+ if (this.has_loaded || this.is_loading) return;
26
+
27
+ this.is_loading = true;
28
+
29
+ let view = new ViewGlue(this.endpoint, {});
30
+ await view.render_inner(this.$refs.child_container);
31
+
32
+ this.has_loaded = true;
33
+ this.is_loading = false;
34
+ },
35
+
36
+ handle_toggle_row_state(event) {
37
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
38
+ this.is_open = event.detail.is_open;
39
+
40
+ if (this.is_open) {
41
+ this.load_children();
42
+ }
43
+ }
44
+ }
45
+ }"
46
+ @toggle-row-state.window="handle_toggle_row_state($event)"
47
+ x-show="is_open"
48
+ x-cloak
49
+ >
50
+ <div x-show="is_loading" class="p-3">
51
+ <div class="mb-2">
52
+ <div class="skeleton-box" style="height: 20px; width: 80%; margin-left: 20px;"></div>
53
+ <div class="skeleton-box" style="height: 20px; width: 80%; margin-left: 20px;"></div>
54
+ <div class="skeleton-box" style="height: 20px; width: 80%; margin-left: 20px;"></div>
55
+ </div>
56
+ </div>
57
+
58
+ <div x-show="!is_loading" x-ref="child_container"></div>
59
+ </div>
@@ -0,0 +1,45 @@
1
+ <div
2
+ class="{% block row_class %}{% endblock %}"
3
+ x-data="{
4
+ row_id: null,
5
+ table_id: null,
6
+ is_open: false,
7
+ has_children: false,
8
+
9
+ init() {
10
+ this.row_id = this.$id('expandable-row');
11
+ this.$el.setAttribute('data-row-id', this.row_id);
12
+ this.table_id = this.$el.closest('[data-table-id]').dataset.tableId;
13
+ },
14
+
15
+ handle_child_registered(event) {
16
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
17
+ this.has_children = true;
18
+ }
19
+ },
20
+
21
+ handle_toggle_row_state(event) {
22
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
23
+ this.is_open = event.detail.is_open;
24
+ }
25
+ }
26
+ }"
27
+ @child-registered.window="handle_child_registered($event)"
28
+ @toggle-row-state.window="handle_toggle_row_state($event)"
29
+ >
30
+ <div class="d-flex align-items-center gap-2">
31
+ <template x-if="has_children">
32
+ <i
33
+ :class="is_open ? 'bi-chevron-down' : 'bi-chevron-right'"
34
+ @click="$dispatch('toggle-row', { row_id: row_id, table_id: table_id })"
35
+ class="bi cursor-pointer text-primary"
36
+ ></i>
37
+ </template>
38
+
39
+ {% block row_header %}{% endblock %}
40
+ </div>
41
+
42
+ {% block row_content %}{% endblock %}
43
+
44
+ {% block child_row %}{% endblock %}
45
+ </div>
@@ -0,0 +1,17 @@
1
+ <div class="row mt-3">
2
+ <div class="col-6 text-start">
3
+ <span class="fs-7 text-app-secondary">
4
+ Showing <span x-text="loaded_count"></span> of <span x-text="total_count"></span> rows
5
+ </span>
6
+ </div>
7
+
8
+ <div class="col-md-6 text-end" style="min-height: 2rem;">
9
+ {% block toolbar_action %}
10
+ <span class="fs-7 text-app-secondary" x-cloak x-show="selected_rows.size > 0">
11
+ <span x-text="selected_rows.size"></span> selected
12
+ </span>
13
+
14
+ {% block table_toolbar_button %}{% endblock %}
15
+ {% endblock %}
16
+ </div>
17
+ </div>
@@ -0,0 +1,9 @@
1
+ <th
2
+ data-breakpoint="{{ breakpoint|default:'' }}"
3
+ {% if sort_key %}@click="sort_by('{{ sort_key }}')"{% endif %}
4
+ class="{% if sort_key %}cursor-pointer{% endif %} {% if breakpoint %}d-none d-{{ breakpoint }}-table-cell{% endif %} user-select-none {{ extra_class|default:'' }}"
5
+ style="{{ style|default:'' }}"
6
+ x-init="let index = Array.from($el.parentElement.children).indexOf($el); $dispatch('header-registered', { index: index, breakpoint: '{{ breakpoint|default:'' }}' || null });"
7
+ >
8
+ {{ label }} {% if sort_key %}<i :class="get_sort_icon('{{ sort_key }}')" class="bi"></i>{% endif %}
9
+ </th>
@@ -0,0 +1,13 @@
1
+ <tbody x-cloak x-show="is_loading && !is_refreshing">
2
+ <template x-if="column_count > 0">
3
+ <template x-for="i in skeleton_count" :key="i">
4
+ <tr class="skeleton-row" :style="average_row_height > 0 ? `height: ${average_row_height}px;` : ''">
5
+ <template x-for="j in column_count" :key="j">
6
+ <td class="align-middle">
7
+ <div class="skeleton-box" style="height: 25px; width: 90%;"></div>
8
+ </td>
9
+ </template>
10
+ </tr>
11
+ </template>
12
+ </template>
13
+ </tbody>
@@ -0,0 +1,13 @@
1
+ <tbody x-cloak x-show="is_refreshing">
2
+ <template x-if="column_count > 0">
3
+ <template x-for="i in skeleton_count" :key="i">
4
+ <tr class="skeleton-row" :style="average_row_height > 0 ? `height: ${average_row_height}px;` : ''">
5
+ <template x-for="j in column_count" :key="j">
6
+ <td class="align-middle">
7
+ <div class="skeleton-box" style="height: 25px; width: 90%;"></div>
8
+ </td>
9
+ </template>
10
+ </tr>
11
+ </template>
12
+ </template>
13
+ </tbody>
@@ -0,0 +1,94 @@
1
+ {% if forloop.first %}
2
+ <div
3
+ x-data="{
4
+ init() {
5
+ let table_id = this.$el.closest('[data-table-id]').dataset.tableId;
6
+ this.$dispatch('total-count-updated', { total_count: {{ total_count }}, batch_size: {{ batch_size|default:25 }}, table_id: table_id });
7
+ }
8
+ }"
9
+ style="display: none;"
10
+ ></div>
11
+ {% endif %}
12
+
13
+ <tr
14
+ class="align-middle {% block unified_row_background_colour %}{% endblock %}"
15
+ data-row-id="{{ row.pk }}"
16
+ x-data="{
17
+ row_id: '{{ row.pk }}',
18
+ table_id: null,
19
+ is_checked: false,
20
+ is_open: false,
21
+ has_children: false,
22
+
23
+ init() {
24
+ this.table_id = this.$el.closest('[data-table-id]').dataset.tableId;
25
+ this.$dispatch('row-mounted', { row_id: this.row_id, row_element: this.$el, table_id: this.table_id });
26
+ },
27
+
28
+ handle_checkbox_change() {
29
+ this.is_checked = !this.is_checked;
30
+
31
+ if (this.is_checked) {
32
+ this.$dispatch('row-selected', { row_id: this.row_id, table_id: this.table_id });
33
+ } else {
34
+ this.$dispatch('row-deselected', { row_id: this.row_id, table_id: this.table_id });
35
+ }
36
+ },
37
+
38
+ handle_child_registered(event) {
39
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
40
+ this.has_children = true;
41
+ }
42
+ },
43
+
44
+ handle_deselect_all_rows(event) {
45
+ if (!event.detail || event.detail.table_id === this.table_id) {
46
+ this.is_checked = false;
47
+ this.$dispatch('row-deselected', { row_id: this.row_id, table_id: this.table_id });
48
+ }
49
+ },
50
+
51
+ handle_select_all_rows(event) {
52
+ if (!event.detail || event.detail.table_id === this.table_id) {
53
+ this.is_checked = true;
54
+ this.$dispatch('row-selected', { row_id: this.row_id, table_id: this.table_id });
55
+ }
56
+ },
57
+
58
+ handle_toggle_row_state(event) {
59
+ if (event.detail.row_id === this.row_id && (!event.detail.table_id || event.detail.table_id === this.table_id)) {
60
+ this.is_open = event.detail.is_open;
61
+ }
62
+ },
63
+ }"
64
+ @child-registered.window="handle_child_registered($event)"
65
+ @deselect-all-rows.window="handle_deselect_all_rows($event)"
66
+ @select-all-rows.window="handle_select_all_rows($event)"
67
+ @toggle-row-state.window="handle_toggle_row_state($event)"
68
+ >
69
+ <td>
70
+ <input
71
+ :checked="is_checked"
72
+ @change="handle_checkbox_change()"
73
+ type="checkbox"
74
+ >
75
+ </td>
76
+
77
+ <td>
78
+ <template x-if="has_children">
79
+ <i
80
+ :class="is_open ? 'bi-chevron-down' : 'bi-chevron-right'"
81
+ @click="$dispatch('toggle-row', { row_id: row_id, table_id: table_id })"
82
+ class="bi cursor-pointer"
83
+ ></i>
84
+ </template>
85
+ </td>
86
+
87
+ {% block table_cells %}{% endblock %}
88
+ </tr>
89
+
90
+ <tr class="align-middle" data-row-id="{{ row.pk }}">
91
+ <td class="p-0" x-effect="$el.setAttribute('colspan', column_count)">
92
+ {% block child_row %}{% endblock %}
93
+ </td>
94
+ </tr>
@@ -0,0 +1 @@
1
+ <div style="height: 10px;" x-ref="infinite_scroll_trigger"></div>
@@ -0,0 +1,11 @@
1
+ <tbody x-show="!is_loading && !is_refreshing && loaded_count === 0 && total_count === 0" x-transition style="display: none;">
2
+ <tr>
3
+ <td class="text-center text-app-secondary py-4" x-effect="$el.setAttribute('colspan', column_count)">
4
+ <span class="fs-6">
5
+ {% block table_no_data %}
6
+ No data available
7
+ {% endblock %}
8
+ </span>
9
+ </td>
10
+ </tr>
11
+ </tbody>
@@ -0,0 +1 @@
1
+ <span class="badge rounded-pill fw-normal border border-app-primary text-app-primary">{{ tag|title }} {{ weight|default:1 }}</span>