3387418332
Каждому виджету можно задать ширину: «Полная» (на всю строку) или «½».
Виджеты выстраиваются в flex-сетку: два «½» встают в один ряд, «Полная»
занимает строку целиком. На узких экранах половинные переносятся в столбик.
- DashboardLayout.widget_widths (JSON {ключ: half|full}) + миграция 0022;
поле widgets не тронуто — обратная совместимость сохранена
- core/dashboard: layout_widths, ширина в build_dashboard_context/
customize_rows, save_layout(widths=...), normalize_width
- dashboard.html: flex-wrap сетка по ширине; customize: переключатель ½/полная
- tests: 6 (сохранение/чтение ширин, нормализация, дефолт, контекст, POST)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
73 lines
5.1 KiB
HTML
73 lines
5.1 KiB
HTML
{% load i18n %}
|
||
<div class="page-content">
|
||
|
||
<div style="display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1.25rem;">
|
||
<div>
|
||
<h1 style="font-size: 1.5rem; font-weight: 700; color: var(--color-foreground); margin-bottom: 0.25rem;">{% trans "Настройка дашборда" %}</h1>
|
||
<p style="font-size: 0.875rem; color: var(--color-muted-foreground);">{% trans "Включите нужные виджеты, задайте ширину и порядок. Два виджета «½» встают в один ряд, «Полная» — на всю ширину." %}</p>
|
||
</div>
|
||
<button class="btn btn-secondary" style="border: 1px solid var(--color-border); font-size: 0.8125rem; white-space: nowrap;"
|
||
hx-get="/dashboard/" hx-target="#main-content" hx-push-url="/dashboard/" hx-indicator="#loader">← {% trans "К дашборду" %}</button>
|
||
</div>
|
||
|
||
<form method="post" action="/dashboard/customize/"
|
||
hx-post="/dashboard/customize/" hx-target="#main-content" hx-push-url="/dashboard/" hx-indicator="#loader"
|
||
onsubmit="dashCollectOrder()">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="order" id="dash-order" value="">
|
||
|
||
<ul id="dash-widget-list" style="list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 0.5rem; max-width: 44rem;">
|
||
{% for row in rows %}
|
||
<li class="dash-widget-row" data-key="{{ row.widget.key }}"
|
||
style="display: flex; align-items: center; gap: 1rem; background: var(--color-card); border: 1px solid var(--color-border); border-radius: 0.625rem; padding: 0.875rem 1rem; box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);">
|
||
<label style="display: flex; align-items: flex-start; gap: 0.75rem; flex: 1; cursor: pointer; min-width: 0;">
|
||
<input type="checkbox" name="enabled" value="{{ row.widget.key }}" {% if row.enabled %}checked{% endif %} style="margin-top: 0.2rem; flex-shrink: 0;">
|
||
<span style="min-width: 0;">
|
||
<span style="font-weight: 600; font-size: 0.875rem; color: var(--color-card-foreground);">{{ row.widget.label }}</span>
|
||
<span style="display: block; font-size: 0.8125rem; color: var(--color-muted-foreground); margin-top: 0.125rem;">{{ row.widget.description }}</span>
|
||
</span>
|
||
</label>
|
||
<div style="display: inline-flex; flex-shrink: 0; border: 1px solid var(--color-border); border-radius: 0.375rem; overflow: hidden; font-size: 0.75rem;">
|
||
<label style="padding: 0.25rem 0.5rem; cursor: pointer; color: var(--color-muted-foreground);">
|
||
<input type="radio" name="width_{{ row.widget.key }}" value="full" {% if row.width == 'full' %}checked{% endif %} style="margin-right: 0.2rem;">{% trans "Полная" %}
|
||
</label>
|
||
<label style="padding: 0.25rem 0.5rem; cursor: pointer; color: var(--color-muted-foreground); border-left: 1px solid var(--color-border);">
|
||
<input type="radio" name="width_{{ row.widget.key }}" value="half" {% if row.width == 'half' %}checked{% endif %} style="margin-right: 0.2rem;">½
|
||
</label>
|
||
</div>
|
||
<div style="display: flex; gap: 0.25rem; flex-shrink: 0;">
|
||
<button type="button" class="btn btn-ghost btn-icon" title="{% trans 'Выше' %}" onclick="dashMove(this, -1)">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m18 15-6-6-6 6"/></svg>
|
||
</button>
|
||
<button type="button" class="btn btn-ghost btn-icon" title="{% trans 'Ниже' %}" onclick="dashMove(this, 1)">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m6 9 6 6 6-6"/></svg>
|
||
</button>
|
||
</div>
|
||
</li>
|
||
{% endfor %}
|
||
</ul>
|
||
|
||
<div style="display: flex; gap: 0.75rem; margin-top: 1.25rem;">
|
||
<button type="submit" class="btn btn-primary">{% trans "Сохранить" %}</button>
|
||
<button type="submit" name="reset" value="1" class="btn btn-secondary" style="border: 1px solid var(--color-border);">{% trans "Сбросить к стандартному" %}</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
function dashMove(btn, dir) {
|
||
var li = btn.closest('.dash-widget-row');
|
||
var list = li.parentNode;
|
||
if (dir < 0 && li.previousElementSibling) {
|
||
list.insertBefore(li, li.previousElementSibling);
|
||
} else if (dir > 0 && li.nextElementSibling) {
|
||
list.insertBefore(li.nextElementSibling, li);
|
||
}
|
||
}
|
||
function dashCollectOrder() {
|
||
var rows = document.querySelectorAll('#dash-widget-list .dash-widget-row');
|
||
var keys = Array.prototype.map.call(rows, function (li) { return li.dataset.key; });
|
||
document.getElementById('dash-order').value = keys.join(',');
|
||
}
|
||
</script>
|