Modernise l'UI du formulaire modal d'ajout/édition de ligne comptable

Le formulaire natif Drupal (Menu settings, URL alias, Authoring
information, tabledrag/drag-handle pour des tableaux de 1-3 lignes,
"Title" affiché tel quel) était pensé pour un éditeur de contenu
générique, pas pour la saisie numérique quotidienne d'une ligne
comptable. Sans toucher au Form API (validation, structure des champs
inchangées) :

- Grille compacte à 3 colonnes (Date+Type, Client, Montant HT/Facture/
  TTC en ligne), au lieu de l'empilement vertical par défaut.
- Menu settings / URL alias / Authoring information / Published
  masqués via #access -- jamais utilisés pour une ligne_comptable.
- "Title" relabellisé en "Libellé court" avec une description qui
  explicite son rôle de repli quand "Notes / détail" est vide (déjà le
  comportement de home.js/dashboard*.js, jusque-là invisible côté
  formulaire).
- Répartition / Entrée liée : poignée de glisser-déposer et bascule
  "Show row weights" masquées (l'ordre n'affecte jamais la somme ni
  l'affichage), boutons Ajouter/Retirer/Dupliquer restylés.

Classe CSS partagée `figli-ledger-form` ajoutée par form_alter plutôt
que de cibler la classe générée par Drupal, différente entre le
formulaire d'ajout (node-ligne-comptable-form) et celui d'édition
(node-ligne-comptable-edit-form).
This commit is contained in:
2026-09-07 09:57:37 +02:00
parent 9234050a68
commit 4ce1b12099
3 changed files with 264 additions and 0 deletions
@@ -0,0 +1,220 @@
/* Ligne comptable add/edit form (node/add/ligne_comptable, node/X/edit --
usually seen inside the "+ Ajouter une ligne" / "Modifier" AJAX modal
from /lignes, see js/home.js's openAddForm/openEditForm). The default
Drupal/Gin admin form widgets (tabledrag drag handles, the paragraphs
"Toggle Actions" dropdown, vertical tabs rendered as an accordion) are
built for content editors managing arbitrary nodes, not for the small
numeric entry form associates fill in daily. This only restyles it
into a compact grid -- no Form API behavior, validation, or field
structure changes here (those live in figli_compta_ledger.module,
which also hides the "advanced" accordion and "Published" checkbox
entirely rather than just visually, since neither is ever used for a
ligne_comptable).
Explicit local colors rather than Gin's --gin-* custom properties --
this form isn't scoped inside #figli-home-app/#figli-dashboard-app, so
the same "don't trust Gin's vars on custom routes" lesson from
home.css/dashboard.css applies here too. */
.figli-ledger-form {
--flform-border: #dcdee2;
--flform-border-soft: #e8eaed;
--flform-label: #4b5563;
--flform-text: #1a1a1a;
--flform-bg-subtle: #f7f8fa;
--flform-accent: #2f6f4f;
--flform-danger: #b3261e;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.9rem 1rem;
max-width: 44rem;
margin: 0 auto;
padding: 0.15rem 0.1rem 0.5rem;
color: var(--flform-text);
}
html.gin--dark-mode .figli-ledger-form {
--flform-border: #3d3e42;
--flform-border-soft: #333438;
--flform-label: #a1a5ab;
--flform-text: #e8e9ea;
--flform-bg-subtle: #2a2b2e;
--flform-accent: #5fb98a;
--flform-danger: #ff6b6b;
}
/* Wrapper divs Drupal still renders even though they're always empty
here (single-language site, path aliases handled elsewhere, status
hidden via #access in the module) -- without this they'd each still
claim an empty grid cell. */
.figli-ledger-form > .field--name-langcode,
.figli-ledger-form > .field--name-path,
.figli-ledger-form > .entity-content-form-footer,
.figli-ledger-form > .form-actions {
display: none;
}
/* Row layout. Everything not listed here (Montant HT / N° Facture /
Montant TTC) is left to grid auto-placement, which -- given they're
the only three single-column items appearing consecutively in DOM
order -- lines them up as one row of three for free. */
.figli-ledger-form > .field--name-title,
.figli-ledger-form > .field--name-field-client,
.figli-ledger-form > .field--name-field-entree-liee,
.figli-ledger-form > .field--name-field-repartition,
.figli-ledger-form > .field--name-field-notes,
.figli-ledger-form > .field--name-field-flag {
grid-column: 1 / -1;
}
.figli-ledger-form > .field--name-field-date-ligne {
grid-column: 1 / 2;
}
.figli-ledger-form > .field--name-field-type-ligne {
grid-column: 2 / 4;
}
/* Field basics */
.figli-ledger-form .form-item__label {
font-size: 0.78rem;
font-weight: 600;
color: var(--flform-label);
margin-bottom: 0.2rem;
display: inline-block;
}
.figli-ledger-form .form-item__description {
font-size: 0.72rem;
color: var(--flform-label);
margin-top: 0.2rem;
}
.figli-ledger-form input.form-element,
.figli-ledger-form select.form-element,
.figli-ledger-form textarea.form-element {
width: 100%;
box-sizing: border-box;
border: 1px solid var(--flform-border);
border-radius: 6px;
padding: 0.4rem 0.55rem;
font-size: 0.88rem;
background: transparent;
color: var(--flform-text);
}
.figli-ledger-form input.form-element:focus,
.figli-ledger-form select.form-element:focus,
.figli-ledger-form textarea.form-element:focus {
outline: 2px solid var(--flform-accent);
outline-offset: 1px;
}
.figli-ledger-form input[type="date"].form-element {
width: auto;
min-width: 9.5rem;
}
.figli-ledger-form textarea.form-element {
resize: vertical;
}
/* Multi-value tables (Répartition, Entrée client liée): drop the drag
handle and the "Show row weights" accessibility toggle for it -- row
order never affects the répartition sum or anything displayed
downstream, so with the usual 1-3 rows this is pure clutter. Remove
isn't affected: it stays in the per-row "Toggle Actions" menu. */
.figli-ledger-form .tabledrag-toggle-weight-wrapper,
.figli-ledger-form td.field-multiple-drag {
display: none;
}
.figli-ledger-form table.field-multiple-table {
width: 100%;
border-collapse: collapse;
background: var(--flform-bg-subtle);
border: 1px solid var(--flform-border-soft);
border-radius: 8px;
overflow: hidden;
}
.figli-ledger-form table.field-multiple-table thead th {
text-align: left;
padding: 0.5rem 0.6rem 0.35rem;
}
.figli-ledger-form table.field-multiple-table tbody td {
padding: 0.5rem 0.6rem;
vertical-align: top;
border-top: 1px solid var(--flform-border-soft);
}
.figli-ledger-form .field-multiple-table .form-item__label {
visibility: visible;
}
.figli-ledger-form .paragraphs-subform {
display: flex;
gap: 0.6rem;
}
.figli-ledger-form .paragraphs-subform > .js-form-wrapper {
flex: 1;
min-width: 0;
}
/* Buttons: "Ajouter Répartition" / "Add another item", and the per-row
Remove/Duplicate/Collapse actions -- all plain Drupal form-submit
buttons by default, restyled as a small, consistent set instead of
browser-default grey buttons of varying sizes. */
.figli-ledger-form input.button {
font-size: 0.78rem;
padding: 0.3rem 0.65rem;
border-radius: 5px;
border: 1px solid var(--flform-border);
background: transparent;
color: var(--flform-text);
cursor: pointer;
}
.figli-ledger-form input.button:hover {
border-color: var(--flform-accent);
color: var(--flform-accent);
}
.figli-ledger-form .field-add-more-submit {
margin-top: 0.4rem;
}
.figli-ledger-form .paragraphs-dropdown-toggle {
border: none;
background: transparent;
color: var(--flform-label);
cursor: pointer;
font-size: 1rem;
line-height: 1;
padding: 0.1rem 0.3rem;
}
.figli-ledger-form .paragraphs-dropdown-toggle:hover {
color: var(--flform-accent);
}
.figli-ledger-form .paragraphs-dropdown-actions {
display: flex;
flex-direction: column;
gap: 0.2rem;
padding: 0.3rem;
border: 1px solid var(--flform-border);
border-radius: 6px;
background: var(--flform-bg-subtle);
}
/* Field group heading style (the Répartition table's own label, inside
its <th>) -- match the rest of the form's label typography instead of
Drupal's default <h4>. */
.figli-ledger-form table.field-multiple-table h4.form-item__label {
font-size: 0.8rem;
margin: 0;
}
/* A répartition row flagged by the sum-mismatch #validate error (see
figli_compta_ledger_validate_repartition()) -- kept subtle (a red
outline, not a solid fill) to match the same red-liseré convention
used for écarts in the /lignes table itself. */
.figli-ledger-form tr.error,
.figli-ledger-form .has-error {
outline: 1px solid var(--flform-danger);
outline-offset: -1px;
}
/* Form actions (Save), rendered by the AJAX dialog's own button pane
rather than here in most cases, but keep a sane baseline for the
rare full-page (non-modal) view of this form. */
.figli-ledger-form .form-actions input.button--primary {
background: var(--flform-accent);
color: #fff;
border-color: var(--flform-accent);
font-weight: 600;
}
@@ -35,6 +35,13 @@ dashboard_compte:
- core/drupal
- figli_compta_ledger/vue
ledger_form:
css:
theme:
css/ledger-form.css: {}
dependencies:
- core/drupal
admin_chrome:
css:
theme:
@@ -35,6 +35,11 @@ function figli_compta_ledger_form_alter(&$form, FormStateInterface $form_state,
if (!in_array($form_id, ['node_ligne_comptable_form', 'node_ligne_comptable_edit_form'], TRUE)) {
return;
}
// The add and edit forms get different auto-generated classes
// (node-ligne-comptable-form vs. node-ligne-comptable-edit-form) --
// one shared class so css/ledger-form.css can target both without
// duplicating every selector.
$form['#attributes']['class'][] = 'figli-ledger-form';
$form['#validate'][] = 'figli_compta_ledger_validate_repartition';
// Every save must create a revision, unconditionally -- there's no
@@ -71,6 +76,38 @@ function figli_compta_ledger_form_alter(&$form, FormStateInterface $form_state,
];
}
// "Title" is generic Drupal boilerplate that means nothing to a
// freelancer entering an accounting line -- but home.js/dashboard*.js
// do fall back to it as the displayed "Libellé" when Notes / détail is
// left empty, so it can't just be hidden. Relabel it so that
// relationship is explicit instead of a mystery required field.
if (isset($form['title']['widget'][0]['value'])) {
$form['title']['widget'][0]['value']['#title'] = t('Libellé court');
$form['title']['widget'][0]['value']['#description'] = t('Affiché dans le grand livre si "Notes / détail" ci-dessous est laissé vide.');
}
// Menu settings / URL alias / Authoring information: none of this is
// ever used for a ligne_comptable (no menu links, no path aliases, and
// the acting user + revision timestamp are already captured by the
// forced revision in figli_compta_ledger_node_presave() below) -- just
// noise in a form meant to be a quick numeric entry, not a full content
// editing screen. #access rather than deleting the keys: safe no-op if
// a future field ever gets #group => 'advanced'.
if (isset($form['advanced'])) {
$form['advanced']['#access'] = FALSE;
}
// "Published" is meaningless here too -- there's no draft/unpublished
// workflow for ledger lines, every one of them is expected to count
// immediately. Same pattern as the 'revision' field above: force the
// value rather than exposing a checkbox nobody should ever touch.
if (isset($form['status'])) {
$form['status']['widget']['value']['#default_value'] = TRUE;
$form['status']['#access'] = FALSE;
}
$form['#attached']['library'][] = 'figli_compta_ledger/ledger_form';
$request = \Drupal::request();
$wrapper_formats = ['drupal_ajax', 'drupal_modal', 'drupal_dialog'];
$is_ajax_modal = in_array($request->query->get('_wrapper_format'), $wrapper_formats, TRUE)