django-smartbase-admin 1.0.7__py3-none-any.whl → 1.0.9__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.
- django_smartbase_admin/admin/admin_base.py +0 -3
- django_smartbase_admin/admin/widgets.py +28 -20
- django_smartbase_admin/engine/dashboard.py +15 -1
- django_smartbase_admin/engine/filter_widgets.py +3 -2
- django_smartbase_admin/static/sb_admin/dist/chart.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/main.js +1 -1
- django_smartbase_admin/static/sb_admin/dist/main_style.css +1 -1
- django_smartbase_admin/static/sb_admin/src/css/components/_dropdown.css +1 -1
- django_smartbase_admin/static/sb_admin/src/js/autocomplete.js +32 -0
- django_smartbase_admin/static/sb_admin/src/js/chart.js +4 -3
- django_smartbase_admin/static/sb_admin/src/js/main.js +1 -1
- django_smartbase_admin/static/sb_admin/src/js/multiselect.js +2 -2
- django_smartbase_admin/templates/sb_admin/actions/dashboard.html +1 -1
- django_smartbase_admin/templates/sb_admin/actions/list.html +43 -29
- django_smartbase_admin/templates/sb_admin/actions/partials/action_link.html +1 -1
- django_smartbase_admin/templates/sb_admin/dashboard/chart_widget.html +1 -0
- django_smartbase_admin/templates/sb_admin/includes/inline_fieldset.html +48 -36
- django_smartbase_admin/templates/sb_admin/widgets/date.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/includes/related_item_buttons.html +23 -18
- django_smartbase_admin/templates/sb_admin/widgets/multiwidget.html +1 -1
- django_smartbase_admin/templates/sb_admin/widgets/time.html +1 -1
- django_smartbase_admin/views/dashboard_view.py +6 -0
- {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/METADATA +1 -1
- {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/RECORD +26 -26
- {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/LICENSE.md +0 -0
- {django_smartbase_admin-1.0.7.dist-info → django_smartbase_admin-1.0.9.dist-info}/WHEEL +0 -0
|
@@ -94,6 +94,9 @@ export default class Autocomplete {
|
|
|
94
94
|
}, 200))
|
|
95
95
|
|
|
96
96
|
choiceInput.addEventListener('selectItem', (event) => {
|
|
97
|
+
if(!event.target.hasAttribute('multiple')) {
|
|
98
|
+
this.updateEditButtonUrl(event)
|
|
99
|
+
}
|
|
97
100
|
choicesJSListeners.selectItem(event.detail, inputEl)
|
|
98
101
|
})
|
|
99
102
|
choiceInput.addEventListener('addItem', () => {
|
|
@@ -105,6 +108,7 @@ export default class Autocomplete {
|
|
|
105
108
|
choiceInput.addEventListener('change', (e) => {
|
|
106
109
|
if(!e.target.hasAttribute('multiple')) {
|
|
107
110
|
choicesJS.SBwrapperElButton.click()
|
|
111
|
+
this.updateEditButtonUrl(e)
|
|
108
112
|
}
|
|
109
113
|
inputEl.dispatchEvent(new CustomEvent('SBAutocompleteChange'))
|
|
110
114
|
})
|
|
@@ -295,4 +299,32 @@ export default class Autocomplete {
|
|
|
295
299
|
document.getElementById(`${field}-value`).textContent = label
|
|
296
300
|
}
|
|
297
301
|
}
|
|
302
|
+
|
|
303
|
+
updateEditButtonUrl(event) {
|
|
304
|
+
const group = event.srcElement.dataset.autocompleteDataId?.replace("_data", "-wrapper")
|
|
305
|
+
if (!group) return
|
|
306
|
+
|
|
307
|
+
const wrapper = document.getElementById(group)
|
|
308
|
+
const editButton = wrapper?.querySelector('.edit-button')
|
|
309
|
+
const value = event.detail?.value
|
|
310
|
+
|
|
311
|
+
if (editButton && value) {
|
|
312
|
+
let originalUrl = editButton.getAttribute('hx-get') || editButton.dataset.addUrl || ''
|
|
313
|
+
if (!originalUrl) return
|
|
314
|
+
|
|
315
|
+
const newUrl = originalUrl
|
|
316
|
+
.replace(/\/add\/\?/, `/${value}/change/?`)
|
|
317
|
+
.replace(/\/\d+\/change\/\?/, `/${value}/change/?`)
|
|
318
|
+
|
|
319
|
+
const newEditButton = editButton.cloneNode(true)
|
|
320
|
+
newEditButton.setAttribute('hx-get', newUrl)
|
|
321
|
+
newEditButton.classList.remove('hidden')
|
|
322
|
+
|
|
323
|
+
editButton.replaceWith(newEditButton)
|
|
324
|
+
window.htmx.process(newEditButton)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
298
330
|
}
|
|
@@ -24,6 +24,7 @@ class SBAdminChart {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
initChart() {
|
|
27
|
+
this.options.formId = this.options.formId || `${this.options.widgetId}-filter-form`
|
|
27
28
|
const ctx = document.getElementById(`${this.options.widgetId}-chart`)
|
|
28
29
|
this.chart = new Chart(ctx, {
|
|
29
30
|
type: this.options.chartType,
|
|
@@ -47,7 +48,7 @@ class SBAdminChart {
|
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
refreshData() {
|
|
50
|
-
const filterForm = document.
|
|
51
|
+
const filterForm = document.getElementById(this.options.formId)
|
|
51
52
|
const filterData = new FormData(filterForm).entries()
|
|
52
53
|
const filterDataNotEmpty = {}
|
|
53
54
|
for (const [key, value] of filterData) {
|
|
@@ -65,7 +66,7 @@ class SBAdminChart {
|
|
|
65
66
|
.then(res => {
|
|
66
67
|
this.chart.data.labels = res.data.main.labels
|
|
67
68
|
this.chart.data.datasets = this.processDatasets(res.data.main.datasets)
|
|
68
|
-
if (this.chart.data.labels.length
|
|
69
|
+
if (this.chart.data.labels.length >= 1) {
|
|
69
70
|
this.chart.canvas.classList.remove('!hidden')
|
|
70
71
|
} else {
|
|
71
72
|
this.chart.canvas.classList.add('!hidden')
|
|
@@ -101,7 +102,7 @@ class SBAdminChart {
|
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
initFilters() {
|
|
104
|
-
filterInputValueChangeListener(`[form="${this.options.
|
|
105
|
+
filterInputValueChangeListener(`[form="${this.options.formId}"]`, (event) => {
|
|
105
106
|
this.refreshData()
|
|
106
107
|
filterInputValueChangedUtil(event.target)
|
|
107
108
|
})
|
|
@@ -10,7 +10,7 @@ export default class Multiselect {
|
|
|
10
10
|
const wrapperEl = e.target.closest(this.wrapperSelector)
|
|
11
11
|
const multiselectInput = wrapperEl?.querySelector(selector)
|
|
12
12
|
const isCheckboxClicked = e.target.type === 'checkbox'
|
|
13
|
-
const selectAllEl = wrapperEl
|
|
13
|
+
const selectAllEl = wrapperEl?.querySelector(`.${selectAllClass}`)
|
|
14
14
|
if(multiselectInput && isCheckboxClicked) {
|
|
15
15
|
const checkboxes = Array.from(this.getCheckboxes(multiselectInput))
|
|
16
16
|
if(e.target.classList.contains(selectAllClass)) {
|
|
@@ -20,7 +20,7 @@ export default class Multiselect {
|
|
|
20
20
|
} else {
|
|
21
21
|
selectAllEl.checked = false
|
|
22
22
|
}
|
|
23
|
-
if(!checkboxes.some(el => el.checked)) {
|
|
23
|
+
if(!checkboxes.some(el => el.checked) && selectAllEl) {
|
|
24
24
|
selectAllEl.checked = true
|
|
25
25
|
}
|
|
26
26
|
let checked = []
|
|
@@ -5,34 +5,48 @@
|
|
|
5
5
|
|
|
6
6
|
{% block content %}
|
|
7
7
|
{% block page_header %}
|
|
8
|
-
<div class="list-view-header py-16 md:pb-32 flex items-center max-xs:px-20">
|
|
8
|
+
<div class="list-view-header py-16 md:pb-32 flex flex-wrap items-center max-xs:px-20 gap-x-16 gap-y-8">
|
|
9
9
|
{% block heading %}
|
|
10
10
|
<h1 class="text-24 md:text-30 text-dark-900 font-bold font-heading line-clamp-1 first-letter:uppercase">{{ list_title|capfirst }}</h1>
|
|
11
11
|
{% endblock %}
|
|
12
|
-
<ul class="ml-auto flex gap-8 md:gap-16">
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
{%
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
12
|
+
<ul class="ml-auto flex flex-wrap gap-8 md:gap-16">
|
|
13
|
+
{% if content_context.list_actions %}
|
|
14
|
+
<li class="relative md:hidden">
|
|
15
|
+
<button
|
|
16
|
+
data-bs-toggle="dropdown"
|
|
17
|
+
aria-expanded="false"
|
|
18
|
+
data-bs-popper-placement="bottom-end"
|
|
19
|
+
class="btn btn-empty btn-icon">
|
|
20
|
+
<svg class="w-16 h-16">
|
|
21
|
+
<use xlink:href="#More"></use>
|
|
22
|
+
</svg>
|
|
23
|
+
<span></span>
|
|
24
|
+
</button>
|
|
25
|
+
<div class="dropdown-menu w-248 max-h-432">
|
|
26
|
+
<ul>
|
|
27
|
+
{% block dropdown_actions %}
|
|
28
|
+
{% for list_action in content_context.list_actions %}
|
|
29
|
+
{% if not list_action.sub_actions %}
|
|
30
|
+
<li>
|
|
31
|
+
{% include "sb_admin/actions/partials/action_link.html" with action=list_action view_id=view_id extra_classes="dropdown-menu-link items-center" %}
|
|
32
|
+
</li>
|
|
33
|
+
{% endif %}
|
|
34
|
+
{% endfor %}
|
|
35
|
+
<script>
|
|
36
|
+
(function(){
|
|
37
|
+
if(document.currentScript && !document.currentScript.previousElementSibling) {
|
|
38
|
+
const li = document.currentScript.closest('li');
|
|
39
|
+
if(li) {
|
|
40
|
+
li.classList.add('hidden');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}());
|
|
44
|
+
</script>
|
|
45
|
+
{% endblock %}
|
|
46
|
+
</ul>
|
|
47
|
+
</div>
|
|
48
|
+
</li>
|
|
49
|
+
{% endif %}
|
|
36
50
|
|
|
37
51
|
{% block actions %}
|
|
38
52
|
{% for list_action in content_context.list_actions %}
|
|
@@ -40,15 +54,15 @@
|
|
|
40
54
|
<button
|
|
41
55
|
data-bs-toggle="dropdown"
|
|
42
56
|
aria-expanded="false"
|
|
43
|
-
data-bs-popper-placement="bottom"
|
|
44
|
-
class="btn">
|
|
57
|
+
data-bs-popper-placement="bottom-end"
|
|
58
|
+
class="btn btn-icon">
|
|
45
59
|
{% if list_action.icon %}
|
|
46
60
|
<svg class="w-16 h-16 md:mr-8">
|
|
47
61
|
<use xlink:href="#{{ list_action.icon }}"></use>
|
|
48
62
|
</svg>
|
|
49
63
|
{% endif %}
|
|
50
64
|
<span>{{ list_action.title }}</span>
|
|
51
|
-
<svg class="w-16 h-16 ml-4">
|
|
65
|
+
<svg class="w-16 h-16 ml-4 max-sm:hidden">
|
|
52
66
|
<use xlink:href="#Down"></use>
|
|
53
67
|
</svg>
|
|
54
68
|
</button>
|
|
@@ -56,7 +70,7 @@
|
|
|
56
70
|
<ul>
|
|
57
71
|
{% for list_sub_action in list_action.sub_actions %}
|
|
58
72
|
<li>
|
|
59
|
-
|
|
73
|
+
{% include "sb_admin/actions/partials/action_link.html" with action=list_sub_action view_id=view_id extra_classes="dropdown-menu-link" %}
|
|
60
74
|
</li>
|
|
61
75
|
{% endfor %}
|
|
62
76
|
</ul>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
<script>
|
|
18
18
|
window.SBAdminChart = window.SBAdminChart || {};
|
|
19
19
|
window.SBAdminChart["{{ widget_id }}"] = new window.SBAdminChartClass({
|
|
20
|
+
"formId": "{{ widget_id }}-filter-form",
|
|
20
21
|
"widgetId": "{{ widget_id }}",
|
|
21
22
|
"ajaxUrl": "{{ ajax_url }}",
|
|
22
23
|
"chartType": "{{ chart_type }}",
|
|
@@ -1,46 +1,58 @@
|
|
|
1
1
|
{% load sb_admin_tags %}
|
|
2
2
|
|
|
3
|
-
{%
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
{% block content %}
|
|
4
|
+
{% block header %}
|
|
5
|
+
{% if fieldset.name %}
|
|
6
|
+
<header class="mb-24">
|
|
7
|
+
<span class="text-dark-900">{{ fieldset.name }}</span>
|
|
8
|
+
<div class="ml-auto flex items-center">
|
|
9
|
+
{% block actions %}
|
|
10
|
+
{% for action in fieldset_context.actions %}
|
|
11
|
+
<a href="{{ action.url }}" class="btn btn-small">{{ action.title }}</a>
|
|
12
|
+
{% endfor %}
|
|
13
|
+
{% endblock %}
|
|
14
|
+
{% block description %}
|
|
15
|
+
{% if fieldset.description %}
|
|
16
|
+
<div class="js-tooltip ml-8" data-bs-toggle="tooltip" data-bs-placement="bottom" title="{{ fieldset.description|safe }}">
|
|
17
|
+
<svg class="w-20 h-20">
|
|
18
|
+
<use xlink:href="#Help"></use>
|
|
19
|
+
</svg>
|
|
20
|
+
</div>
|
|
21
|
+
{% endif %}
|
|
22
|
+
{% endblock %}
|
|
15
23
|
</div>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{%
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
</header>
|
|
25
|
+
{% endif %}
|
|
26
|
+
{% endblock %}
|
|
27
|
+
|
|
28
|
+
{% block lines %}
|
|
29
|
+
{% for line in fieldset %}
|
|
30
|
+
<div class="field{% if forloop.last %} -mb-24{% endif %}{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
|
|
31
|
+
{% if line.fields|length > 1 %}
|
|
32
|
+
<div class="flex max-xs:flex-wrap gap-x-16">{% endif %}
|
|
33
|
+
|
|
34
|
+
{% for field in line %}
|
|
35
|
+
<div class="mb-16 sm:mb-24 max-xs:w-full sm:flex-1{% if field.field.is_hidden or field.field|is_row_class_field %} hidden{% endif %}">
|
|
36
|
+
{% if field.is_readonly %}
|
|
37
|
+
{% call_method field "contents" request %}
|
|
38
|
+
{% else %}
|
|
39
|
+
{{ field.field }}
|
|
40
|
+
{% if not line.fields|length == 1 %}{{ field.errors }}{% endif %}
|
|
41
|
+
{% endif %}
|
|
42
|
+
</div>
|
|
43
|
+
{% endfor %}
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
{% else %}
|
|
30
|
-
{{ field.field }}
|
|
31
|
-
{% if not line.fields|length == 1 %}{{ field.errors }}{% endif %}
|
|
45
|
+
{% if line.fields|length == 1 and line.errors %}
|
|
46
|
+
<div class="-mt-16 mb-24">
|
|
47
|
+
{{ line.errors }}
|
|
48
|
+
</div>
|
|
32
49
|
{% endif %}
|
|
50
|
+
{% if line.fields|length > 1 %}</div>{% endif %}
|
|
33
51
|
</div>
|
|
34
52
|
{% endfor %}
|
|
35
53
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{{ line.errors }}
|
|
39
|
-
</div>
|
|
40
|
-
{% endif %}
|
|
41
|
-
{% if line.fields|length > 1 %}</div>{% endif %}
|
|
42
|
-
</div>
|
|
43
|
-
{% endfor %}
|
|
44
|
-
{% block additional_fields %}
|
|
54
|
+
{% endblock %}
|
|
55
|
+
{% block additional_fields %}
|
|
45
56
|
|
|
57
|
+
{% endblock %}
|
|
46
58
|
{% endblock %}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{% include 'sb_admin/widgets/includes/field_label.html' %}
|
|
2
|
-
<div class="relative
|
|
2
|
+
<div class="relative flex">
|
|
3
3
|
{% include "sb_admin/widgets/input.html" %}
|
|
4
4
|
<label class="absolute right-10 top-0 bottom-0 h-full flex items-center pointer-events-none" for="{{ widget.attrs.id }}">
|
|
5
5
|
<svg class="w-20 h-20">
|
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
{% load i18n %}
|
|
2
|
-
<div class="flex items-center gap-4 flex-shrink-0">
|
|
3
|
-
{% if widget.attrs.related_edit_url %}
|
|
4
|
-
<a
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
<div class="flex items-center gap-4 flex-shrink-0 related-item-buttons">
|
|
3
|
+
{% if widget.attrs.related_edit_url or widget.attrs.related_add_url %}
|
|
4
|
+
<a
|
|
5
|
+
class="btn btn-icon edit-button {% if not widget.attrs.related_edit_url %}hidden{% endif %}"
|
|
6
|
+
title="{% trans 'Edit' %}"
|
|
7
|
+
{% if widget.attrs.related_edit_url %}
|
|
8
|
+
hx-get="{{ widget.attrs.related_edit_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
|
|
9
|
+
{% endif %}
|
|
10
|
+
{% if widget.attrs.related_add_url and not widget.attrs.related_edit_url %}
|
|
11
|
+
data-add-url="{{ widget.attrs.related_add_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
|
|
12
|
+
{% endif %}
|
|
13
|
+
data-bs-toggle="modal"
|
|
14
|
+
data-bs-target="#sb-admin-modal"
|
|
15
|
+
hx-target="#sb-admin-modal .modal-content"
|
|
16
|
+
hx-swap="innerHTML">
|
|
12
17
|
<svg class="w-20 h-20">
|
|
13
18
|
<use xlink:href="#Preview-open"></use>
|
|
14
19
|
</svg>
|
|
15
20
|
</a>
|
|
16
21
|
{% endif %}
|
|
17
22
|
{% if widget.attrs.related_add_url %}
|
|
18
|
-
<a
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<a
|
|
24
|
+
class="btn btn-icon"
|
|
25
|
+
title="{% trans 'Add' %}"
|
|
26
|
+
data-bs-toggle="modal"
|
|
27
|
+
data-bs-target="#sb-admin-modal"
|
|
28
|
+
hx-get="{{ widget.attrs.related_add_url }}?_popup=1&sbadmin_is_modal=1&sb_admin_source_field={{ widget.attrs.id }}"
|
|
29
|
+
hx-target="#sb-admin-modal .modal-content"
|
|
30
|
+
hx-swap="innerHTML">
|
|
26
31
|
<svg class="w-20 h-20">
|
|
27
32
|
<use xlink:href="#Add-one"></use>
|
|
28
33
|
</svg>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{% include 'sb_admin/widgets/includes/field_label.html' %}
|
|
2
|
-
<div class="relative
|
|
2
|
+
<div class="relative flex time-wrapper">
|
|
3
3
|
{% include "sb_admin/widgets/input.html" %}
|
|
4
4
|
<label class="absolute right-10 top-0 bottom-0 h-full flex items-center pointer-events-none" for="{{ widget.attrs.id }}">
|
|
5
5
|
<svg class="w-20 h-20">
|
|
@@ -11,6 +11,7 @@ class SBAdminDashboardView(SBAdminView):
|
|
|
11
11
|
menu_action = Action.DASHBOARD.value
|
|
12
12
|
widgets = None
|
|
13
13
|
title = None
|
|
14
|
+
direct_sub_views = None
|
|
14
15
|
|
|
15
16
|
def __init__(self, title=None, widgets=None) -> None:
|
|
16
17
|
super().__init__()
|
|
@@ -27,16 +28,21 @@ class SBAdminDashboardView(SBAdminView):
|
|
|
27
28
|
view.init_view_dynamic(request, request_data, **kwargs)
|
|
28
29
|
|
|
29
30
|
def get_sub_views(self, configuration):
|
|
31
|
+
self.direct_sub_views = []
|
|
30
32
|
self.sub_views = []
|
|
31
33
|
for idx, widget_view in enumerate(self.widgets):
|
|
32
34
|
widget_view.widget_id = f"{self.get_id()}_{idx}"
|
|
33
35
|
widget_view.init_widget_static(configuration)
|
|
36
|
+
widget_view_sub_views = widget_view.get_sub_views(configuration) or []
|
|
34
37
|
self.sub_views.append(widget_view)
|
|
38
|
+
self.direct_sub_views.append(widget_view)
|
|
39
|
+
self.sub_views.extend(widget_view_sub_views)
|
|
35
40
|
return self.sub_views
|
|
36
41
|
|
|
37
42
|
def dashboard(self, request, modifier):
|
|
38
43
|
context = self.get_global_context(request)
|
|
39
44
|
context["sub_views"] = self.sub_views
|
|
45
|
+
context["direct_sub_views"] = self.direct_sub_views
|
|
40
46
|
context["title"] = self.get_title()
|
|
41
47
|
return TemplateResponse(
|
|
42
48
|
request,
|