Files
ACORE/templates/pages/dashboard_customize.html
T
lokses 3387418332 dashboard: группировка виджетов по ширине (½ / полная)
Каждому виджету можно задать ширину: «Полная» (на всю строку) или «½».
Виджеты выстраиваются в 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>
2026-09-22 10:47:14 +03:00

73 lines
5.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% 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>