Lay the plan modal out in two columns instead of one tall one

PlanForm grew to seven fields with the billing rework, three of them
short day counts, and the modal ran off the bottom of the screen.

_modal_form.html gains two optional knobs -- box_class to widen the
box, two_columns to grid the fields -- so only the plan create/edit
modals opt in and every other modal renders exactly as before.
Textareas and file inputs still span the full width; below `sm` it
stays single-column, since two columns of form fields on a phone is
worse than a long scroll.

PlanForm's field order is chosen to suit that grid: pairing name with
duration and the two day-counts with each other fills every row
rather than leaving half of one empty next to the full-width
description.

Rebuilt static/css/app.css (unminified, matching how it is committed)
to pick up the new grid utilities.
This commit is contained in:
2026-08-08 19:01:14 +02:00
parent fc6488ce55
commit c2a4380b70
5 changed files with 95 additions and 9 deletions

View File

@@ -83,9 +83,19 @@ class FlagForm(forms.ModelForm):
class PlanForm(forms.ModelForm):
"""Field order is chosen for the two-column modal (see _form_fields.html): description
spans both columns, so pairing name with duration and the two day-counts with each other
fills every row instead of leaving half of one empty.
name | duration_months
description ....................... (spans both)
renewal_lead_days | grace_days
is_trial | is_active
"""
class Meta:
model = Plan
fields = ["name", "description", "duration_months", "renewal_lead_days", "grace_days", "is_trial", "is_active"]
fields = ["name", "duration_months", "description", "renewal_lead_days", "grace_days", "is_trial", "is_active"]
class PlanPriceForm(forms.ModelForm):

View File

@@ -4,14 +4,22 @@
The field loop every card-form and modal-form wrapper shares: label, daisyUI-styled
widget, help text, errors — with checkboxes laid out label-beside-input instead of
label-above. Included with `form`.
Pass `two_columns` to lay the fields out in two columns instead of one — for a form with
several short fields (a handful of day counts, say) that would otherwise make a modal
taller than the screen. Textareas and file inputs always span the full width regardless:
half a row is not enough to type a description into. Single column below `sm`, since two
columns of form fields on a phone is worse than a long scroll.
{% endcomment %}
{% for error in form.non_field_errors %}
<div class="alert alert-error my-2">
<span>{{ error }}</span>
</div>
{% endfor %}
{% for field in form %}
<div class="form-control my-3 w-full">
{% form_field field %}
</div>
{% endfor %}
<div class="{% if two_columns %}grid gap-x-4 sm:grid-cols-2{% endif %}">
{% for field in form %}
<div class="form-control my-3 w-full {% if two_columns and field.widget_type == "textarea" or two_columns and field.widget_type == "clearablefile" %}sm:col-span-2{% endif %}">
{% form_field field %}
</div>
{% endfor %}
</div>

View File

@@ -7,10 +7,15 @@
tag (linked via the `form` attribute) so it can share the `modal-action` row with the
dialog-closing Cancel button without nesting one <form> inside another. `enctype` is
always multipart -- harmless for a form with no file field, required for one that has.
Two optional knobs for a form with a lot of fields, which would otherwise run off the
bottom of the screen: `box_class` widens the box (e.g. "max-w-2xl"), and `two_columns`
lays the fields out in two columns -- growing sideways rather than downwards. They are
meant to be used together; widening alone just makes a tall modal wide as well.
{% endcomment %}
{% trans "Save" as default_submit_label %}
<dialog id="{{ modal_id }}" class="modal">
<div class="modal-box">
<div class="modal-box {{ box_class }}">
<h3 class="text-lg font-bold">{{ title }}</h3>
{% if blurb %}<p class="py-2 text-sm opacity-70">{{ blurb }}</p>{% endif %}
<form method="post" action="{{ action_url }}" id="{{ modal_id }}-form" enctype="multipart/form-data">

View File

@@ -9,7 +9,7 @@
{% block panel %}
{% url 'controlpanel:plan_create' as plan_create_url %}
{% include "controlpanel/_modal_form.html" with modal_id="plan_create_modal" title="New plan" form=plan_form action_url=plan_create_url submit_label="Create plan" submit_icon="plus" %}
{% include "controlpanel/_modal_form.html" with modal_id="plan_create_modal" title="New plan" form=plan_form action_url=plan_create_url submit_label="Create plan" submit_icon="plus" box_class="max-w-2xl" two_columns=True %}
<div class="card mb-6 bg-base-100 shadow">
<div class="card-body">
@@ -84,7 +84,7 @@
{% include "controlpanel/_modal_form.html" with modal_id=plan.pk|dom_id:"plan_price_modal" title="New price — "|add:plan.name form=plan.price_form action_url=plan_price_url submit_label="Add price" submit_icon="euro" %}
{% url 'controlpanel:plan_update' plan.pk as plan_update_url %}
{% include "controlpanel/_modal_form.html" with modal_id=plan.pk|dom_id:"plan_edit_modal" title="Edit "|add:plan.name form=plan.edit_form action_url=plan_update_url submit_label="Save" submit_icon="check" %}
{% include "controlpanel/_modal_form.html" with modal_id=plan.pk|dom_id:"plan_edit_modal" title="Edit "|add:plan.name form=plan.edit_form action_url=plan_update_url submit_label="Save" submit_icon="check" box_class="max-w-2xl" two_columns=True %}
{% endfor %}
<div class="card bg-base-100 shadow">